Select last N rows following a condition - mysql

I've got a database table with logs which has 3 columns:
date | status | projectId
status can be either 0 or 1, primary key is on date and projectID
I'm trying to find out how many times a projectID had status 0 since the last time it was 1.
so if there would be only one projectId
date | status | projectId
1 0 3
2 0 3
3 1 3
4 1 3
5 0 3
6 0 3
this should return 2 (row 5 and 6 are 0 and row 4 is 1)
The thing that makes it hard for me is that I have to maintain the order of date. What would be a good way to tackle such problems, and this one in particular?

Here is how you would do it for one project:
select count(*)
from logs l
where status = 0 and
projectid = 3 and
date > (select max(date) from logs where projectid = 3 and status = 1)
Here is how you would do it for all projects:
select l.projectId, count(l1.projectId)
from logs l left outer join
(select projectId, max(date) as maxdate
from logs
where status = 1
group by projectId
) l1
on l.projectId = l1.projectId and
l.date > l1.date and
l.status = 0
group by l.projectId;

here you have an option in just one select.
http://sqlfiddle.com/#!2/6ce87/11
select *
from logs
where status=0 and date > (select date from logs where status=1 order by date desc limit 1)

Here's one way to get the result for all project_id:
SELECT m.project_id
, COUNT(1) AS mycount
FROM ( SELECT l.project_id
, MAX(l.date) AS latest_date
FROM mytable l
WHERE l.status = 1
) m
JOIN mytable t
ON t.project_id = m.project_id
AND t.date > m.latest_date
AND t.status = 0
If you need only a subset of project_id, the predicate should be added to the WHERE clause in the inline view query:
WHERE l.status = 1
AND l.project_id IN (3,5,7)
EDIT
That query does not return a row if there is no status=0 row after the latest status=1 row. To return a zero count, this could be done with an outer join.
SELECT m.project_id
, COUNT(t.status) AS mycount
FROM ( SELECT l.project_id
, MAX(l.date) AS latest_date
FROM mytable l
WHERE l.status = 1
AND l.project_id IN (3)
) m
LEFT
JOIN mytable t
ON t.project_id = m.project_id
AND t.date > m.latest_date
AND t.status = 0
For optimum performance, the statement could make use of an index with leading columns of project_id and date (in that order) and including the status column, e.g.
ON mytable (`project_id`,`date`,`status`)

Related

How can I retrieve Similar Orders In Mysql?

i need a query that should first look the oldest order which has status 0 (zero). and retrieves all the similar orders of that kind(matches exact total qty, itemSku and number of distinct items ordered).
***OrdersTable***
ID OrderNumber CustomerId Status created_at
1 123456 1 0 2018-01-01
2 234567 1 0 2018-01-02
3 345678 1 0 2018-01-03
4 456789 1 0 2018-01-04
***PurchasedProductsTable***
OrderId itemSku Qty
1 1000001 1
1 1000002 2
2 1000001 3
3 1000001 1
3 1000002 2
4 1000001 3
In the above table the query should first look at the oldest (created_at ASC) order (i.e with Id 1) having status 0 (in order table). and along with that order it should retrieves all the other orders that matches the same itemSku, qty and total distinct items count (in purchasedProducts table).
here order 1 and 3 matches the same itemSKu (1000001 and 1000002) and qty ( 1 and 2) and both have (2) distinct items count respectively so order 1 and 3 should be retrived at first.and when i marked order 1 and 3 as shipped (i.e chang status to 2).
and if i run query again it should retrive similar oders. now order 2 and 4 as order 2 and 4 are similar orders. (have same itemSkus (1000001, Qty (3) and distinct items count (1)).
please help thanks
You have to go trough your tables two times :)
Something like this :
SELECT DISTINCT O2.ID
FROM OrdersTable O1
INNER JOIN PurchasedProductsTable P1 ON O1.ID = P1.OrderId
INNER JOIN PurchasedProductsTable P2 ON P1.itemSku = P2.itemSku
AND P1.Qty = P2.Qty
INNER JOIN OrdersTable O2 ON O2.ID = P2.OrderId
WHERE O1.ID =
(SELECT ID FROM OrdersTable WHERE Status = 0
ORDER BY created_at ASC LIMIT 1)
AND (SELECT COUNT(*) FROM PurchasedProductsTable WHERE OrderId = O1.ID)
= (SELECT COUNT(*) FROM PurchasedProductsTable WHERE OrderId = O2.ID)
ORDER BY O2.ID ASC;
https://www.db-fiddle.com/f/65t9GgSfqMpzNVgnrJp2TR/2
You can get the earliest order via a limit and ordered by the date.
Then you can left join to get that order and any other order that at least has the same items.
Then once you have those order id's from the sub-query result, you can get the order details.
SELECT o.*
FROM
(
SELECT DISTINCT ord2.ID as OrderId
FROM
(
SELECT ID, CustomerId, Status
FROM OrdersTable
WHERE Status = 0
ORDER BY created_at
LIMIT 1
) AS ord1
JOIN PurchasedProductsTable AS pprod1
ON pprod1.OrderId = ord1.ID
LEFT JOIN OrdersTable ord2
ON ord2.CustomerId = ord1.CustomerId
AND ord2.Status = ord1.Status
LEFT JOIN PurchasedProductsTable pprod2
ON pprod2.OrderId = ord2.ID
AND pprod2.itemSku = pprod1.itemSku
AND pprod2.Qty = pprod1.Qty
GROUP BY ord1.CustomerId, ord1.ID, ord2.ID
HAVING COUNT(pprod1.itemSku) = COUNT(pprod2.itemSku)
) q
JOIN OrdersTable AS o ON o.ID = q.OrderId;
Test on RexTester here

Get records GROUPing BY and discarding duplicates

I have a "transaction_status" table:
id transaction_id status
1 12 0
2 13 0
3 12 -1
4 14 0
5 13 -1
6 15 0
[END OF TABLE]
I need to get the transaction ids which only have a record with status=0.
For example this is what I am looking for from the above table:
id transaction_id status
4 14 0
6 15 0
So far I am trying:
SELECT id, transaction_id, status
FROM transaction_status
WHERE status = 0 OR status = -1
ORDER BY status ASC
GROUP BY txn_id
HAVING status = 0
Made a small change in the having clause.
SELECT id, transaction_id, status
FROM transaction_status
WHERE status = 0 OR status = -1
GROUP BY transaction_id
HAVING min(status) = 0
I also removed order by as it does not make any sense
Use GROUP BY and in having clause check for both min and max status is 0.
Query
select `transaction_id`
from `your_table_name`
group by`transaction_id`
having min(`status`) = 0 and max(`status`) = 0;
I think the easiest way to handle this is to use conditional aggregation by transaction_id and count which IDs have only a status of 0 associated with them. Do this in a subquery, and then join back to your main table to retrieve the full matching records which you want.
SELECT t1.*
FROM transaction_status t1
INNER JOIN
(
SELECT transaction_id
FROM transaction_status
GROUP BY transaction_id
HAVING SUM(CASE WHEN status <> 0 THEN 1 ELSE 0 END) = 0
) t2
ON t1.transaction_id = t2.transaction_id
ORDER BY t1.id DESC

On average, how many times a user came before making first purchase?

id(pk) user_id(int) came_to_site(date_time) purchases(int)
1 1 27-8-2016:10:12:23 0
2 2 27-8-2016:10:20:23 0
3 1 28-8-2016:10:12:23 1
4 3 29-8-2016:10:12:23 0
5 4 29-8-2016:11:40:23 0
6 4 30-8-2016:10:12:23 0
7 4 30-8-2016:12:12:23 1
8 1 30-8-2016:12:30:23 1
I have this table, I want to know, on average, how many times a user came before making first purchase.
We can ignore user 2 and 3 because they never made a purchase.
User 1 came 2 times before making a purchase.
User 4 came 3 times before making a purchase.
So average would be (2 + 3)/2 = 2.5
Any idea how can I write such a query?
select avg(cnt)
from
(
select user_id, 1 + count(*) as cnt
from tablename t1
where purchases = 0
and exists (select 1 from tablename t2
where t2.user_id = t1.user_id
and t2.purchases = 1)
and not exists (select 1 from tablename t3
where t3.user_id = t1.user_id
and t3.purchases = 1
and t3.came_to_site < t1.came_to_site)
group by user_id
)
The sub-query counts each user_id that has made a purchase (EXISTS), but not before current row (NOT EXISTS).
At main level, do AVG() to get average number.
Perhaps, depending on dbms, you need to do avg(cnt * 1.0) to avoid integer result.
Find the first purchase date of all users that made a purchase, then join to that:
select avg(visits)
from (select t.user_id, count(*) visits
from (select user_id, min(came_to_site) first
from mytable
where purchases > 0
group by user_id) fpd
join mytable t on t.user_id = fpd.user_id and t.came_to_site < fpd.came_to_site) x
With an index on user_id this will perform pretty well.
You could do :
SELECT AVG(cpt) FROM (
SELECT sales_user.user_id, COUNT(*) AS cpt
FROM (
SELECT *
FROM users
WHERE purchases=1) sales_user
JOIN users ON sales_user.user_id=users.user_id
WHERE users.came_to_site < sales_user.came_to_site
GROUP BY sales_user.user_id);

MySQL - Display null column from child table if all values are not distinct

I have the following tables, for example:
invoices
ID Name
1 A
2 B
3 C
4 D
5 E
transactions
ID Invoice_ID User_ID
1 1 10
2 1 10
3 1 10
4 2 30
5 3 20
6 3 40
7 2 30
8 2 30
9 4 40
10 3 50
Now I want to make a select that will pull the invoices and the user_id from the related transactions, but of course if I do that I won't get all the ids, since they may be distinct but there will be only one column for that. What I want to do is that if there are distinct User_ids, I will display a pre-defined text in the column instead of the actual result.
select invoices.id, invoices.name, transactions.user_id(if there are distinct user_ids -> return null)
from invoices
left join transactions on invoices.id = transactions.invoice_id
and then this would be the result
ID Name User_ID
1 A 10
2 B 30
3 C null
4 D 40
5 E null
Is this possible?
You can do the following :
select
invoices.id,
invoices.name,
IF (
(SELECT COUNT(DISTINCT user_id) FROM transactions WHERE transactions.invoice_id = invoices.id) = 1,
(SELECT MAX(user_id) FROM transactions WHERE transactions.invoice_id = invoices.id),
null
) AS user_id
from invoices
Or, alternatively, you can use the GROUP_CONCAT function to output a comma-separated list of users for each invoice. It is not exactly what you asked, but maybe in fact it will be more useful :
select
invoices.id,
invoices.name,
GROUP_CONCAT(DISTINCT transactions.user_id SEPARATOR ',') AS user_ids
from invoices
left join transactions on invoices.id = transactions.invoice_id
group by invoices.id
Try somethingh like:
select i.id, i.name, t.user_id
from invoices i left join
(
select invoice_ID, User_ID
from transactions
group by invoice_ID
having count(invoice_ID)=1
) t on i.id=t.invoice_id
SQL fiddle
You could list all the transactions that have multiple user ids, like this:
select invoices.id, invoices.name, null
from invoices
left join transactions on invoices.id = transactions.invoice_id having count(distinct transactions.user_id) > 1
Also, I think this CASE might suit your needs here:
select invoices.id, invoices.name,
case when count(distinct transactions.user_id) > 1 then null else transactions.user_id end
from invoices
left join transactions on invoices.id = transactions.invoice_id
group by invoices.id
although, I'm not sure this is syntactically correct

SQL Select where user1 has more than one and user2 has none

Here's my Query to find id's for userid 2, I want to run a query that finds entries where userid=2 and amount>1 AND userid 1 has none of that id
SELECT id, amount FROM collection WHERE userid='2' AND amount>1
I'm not sure how to do an if statement inside a SQL query, but there has to be a way to easily do this.
Any help would be appreciated
If I understand right, you want The list of users having user ID = 2 and amount > 1. This list should ignore the records where ID is not in user ID = 1
Sample Input/Ouput:
ID UserID Amount Returned?
1 2 0 No (Amount 0)
2 2 10 Yes
3 1 10
3 2 5 (No, since ID =3 exists with Userid = 1)
Below Query should help you with it.
SELECT C.ID, C.AMOUNT
FROM COLLECTION C
WHERE C.USERID = 2 AND C.AMOUNT > 1
AND C.ID NOT IN
( SELECT D.ID
FROM COLLECTION D
WHERE D.USERID = 1
);
Fiddle here
"where userid=2 and amount>1 AND userid 1 has none of that id"
You can use NOT EXISTS:
SELECT id, amount
FROM collection c
WHERE c.userid = '2'
AND c.amount > 1
AND NOT EXISTS
(
SELECT 1 FROM collection c2
WHERE c2.userid = '1'
AND c.id = c2.id
)
SELECT id, amount
FROM collection
WHERE userid='2' AND amount > 1 AND
NOT id in (SELECT id FROM collection WHERE userid='1' AND amount > 1)
I'm not sure this is how your database is working, but this is how I would've solved it.