WHERE condition across multiple rows - mysql

I have this table...
--------------------------------------
| user_id | status | status_date |
--------------------------------------
| 1 | Current | 2012-08-01 |
| 1 | Referral | 2012-03-14 |
| 2 | Referral | 2012-04-23 |
| | | |
--------------------------------------
How would I query to find a distinct user_id who has a referral date before 2012-06-30 AND either a current date of after 2012-06-30 or no current status record at all?
Database is MySQL.

You can do this using a LEFT JOIN:
SELECT DISTINCT T.User_ID
FROM T
LEFT JOIN T T2
ON t.User_ID = T2.User_ID
AND t2.Status = 'Current'
WHERE T.Status_Date < '20120630'
AND T.Status = 'Referral'
AND (t2.Status_Date > '20120630' OR t2.Status_date IS NULL)
Or, using GROUP BY with HAVING and COUNT(CASE ...)
SELECT t.User_ID
FROM T
GROUP BY t.user_ID
HAVING COUNT(CASE WHEN t.Status = 'Referral' AND t.Status_Date < '20120630' THEN 1 END) > 0
AND ( COUNT(CASE WHEN t.Status = 'Current' AND t.Status_Date > '20120630' THEN 1 END) > 0
OR COUNT(CASE WHEN t.Status = 'Current' THEN 1 ELSE 0 END) = 0
)
It will depend on your indexes and amount of data as to which performs better, I'd imagine in most cases it will be the former

This should do it:
SELECT DISTINCT user_id
FROM YourTable T
WHERE status = 'Referral'
AND status_date < '2012-06-30'
AND NOT EXISTS (SELECT user_id FROM YourTable
WHERE user_id = T.user_id AND status = 'Current'
AND status_date < '2012-06-30')

Avoid doing innner selects with MySQL. All versions up to and including 5.5 cannot optimize properly with them. Use JOINs:
SELECT distinct t1.user_id
FROM tablename t1
LEFT JOIN tablename t2 on t1.user_id = t2.user_id AND t1.status != t2.status
WHERE t1.status = 'Referral'
AND t1.status_date '2012-06-30'
AND ( (t2.status IS NULL) OR
(t2.status = 'Current' AND t2.status_date > '2012-06-30'));

Related

Complex SQL Query for Inventory System

I have 5 tables:
Items
Inventory
ConsumedItemsMonitoring
DamagedItemsMonitoring
UnaccountedItems
I'm new to Complex SQL queries did some research and asked for help and this is what I my code looks like so far.
SELECT Items.ItemID, Items.Item,
SUM(CASE WHEN DATE(Inventory.ItemTransactionDate) < CURDATE() THEN Inventory.Quantity ELSE 0 END) -
SUM(CASE WHEN DATE(consumeditemmonitoring.TransactionDate) <CURDATE() THEN consumeditemmonitoring.Quantity ELSE 0 end) -
SUM(CASE WHEN DATE(damagedinventory.ItemTransactionDate)<CURDATE() THEN damagedinventory.Quantity ELSE 0 end) -
SUM(CASE WHEN DATE(unaccounteditems.ItemTransactionDate)<CURDATE() THEN unaccounteditems.Quantity ELSE 0 end) AS 'PrevBalance',
SUM(CASE WHEN DATE(Inventory.ItemTransactionDate)=CURDATE() THEN Inventory.Quantity else 0 END) AS 'DeliveredToday',
SUM(CASE WHEN DATE(damagedinventory.ItemTransactionDate)=CURDATE() THEN damagedinventory.Quantity ELSE 0 END) AS 'DamagedToday',
SUM(CASE WHEN DATE(consumeditemmonitoring.TransactionDate)=CURDATE() THEN consumeditemmonitoring.Quantity ELSE 0 END) AS 'ConsumedToday',
SUM(CASE WHEN DATE(unaccounteditems.ItemTransactionDate)=CURDATE() THEN unaccounteditems.Quantity ELSE 0 END) AS 'UnAccountedToday',
SUM(CASE WHEN DATE(Inventory.ItemTransactionDate) < CURDATE() THEN Inventory.Quantity else 0 end)-
SUM(CASE WHEN DATE(consumeditemmonitoring.TransactionDate) < CURDATE() THEN consumeditemmonitoring.Quantity ELSE 0 END)-
SUM(CASE WHEN DATE(damagedinventory.ItemTransactionDate) < CURDATE() THEN damagedinventory.Quantity ELSE 0 END)-
SUM(CASE WHEN DATE(unaccounteditems.ItemTransactionDate) < CURDATE() THEN unaccounteditems.Quantity ELSE 0 END)-
SUM(CASE WHEN DATE(consumeditemmonitoring.TransactionDate) = CURDATE() THEN consumeditemmonitoring.Quantity ELSE 0 END)-
SUM(CASE WHEN DATE(damagedinventory.ItemTransactionDate) = CURDATE() THEN damagedinventory.Quantity ELSE 0 END)-
SUM(CASE WHEN DATE(unaccounteditems.ItemTransactionDate) = CURDATE() THEN unaccounteditems.Quantity ELSE 0 END) +
SUM(CASE WHEN DATE(Inventory.ItemTransactionDate) = CURDATE() then Inventory.Quantity ELSE 0 end) AS 'Total Balance'
FROM Items
LEFT OUTER JOIN consumeditemmonitoring ON consumeditemmonitoring.ItemID = Items.ItemID
LEFT OUTER JOIN damagedinventory ON damagedinventory.ItemID = Items.ItemID
LEFT OUTER JOIN unaccounteditems ON unaccounteditems.ItemID = Items.ItemID
LEFT OUTER JOIN inventory ON inventory.ItemID= Items.ItemID
GROUP BY Items.ItemID
The output looks like some of the table are multiplied.
What you are seeing is a result of how joins work and the fact that the joins are executed before the group by. I can illustrate this with a simplified version of your data.
drop table if exists
items,
items_inventory,
items_consumed,
items_damaged,
items_unaccounted;
create table items (id int);
create table items_inventory(id int,itemid int,qty int);
create table items_consumed(id int,itemid int,qty int);
create table items_damaged(id int,itemid int,qty int);
create table items_unaccounted(id int,itemid int,qty int);
insert into items values(1),(2);
insert into items_inventory values (1,1,10),(2,1,10),(2,2,20);
insert into items_consumed values(1,1,5),(2,2,15);
insert into items_damaged values(1,1,25);
If we run a simple select
select i.id,
ii.id,ii.qty,
ic.id,ic.qty,
id.id,id.qty,
iu.id,iu.qty
from items i
left join items_inventory ii on ii.itemid = i.id
left join items_consumed ic on ic.itemid = i.id
left join items_damaged id on id.itemid = i.id
left join items_unaccounted iu on iu.itemid = i.id
;
we get 2 rows for item 1 even though there is only 1 row for items_consumed
+------+------+------+------+------+------+------+------+------+
| id | id | qty | id | qty | id | qty | id | qty |
+------+------+------+------+------+------+------+------+------+
| 1 | 1 | 10 | 1 | 5 | 1 | 25 | NULL | NULL |
| 1 | 2 | 10 | 1 | 5 | 1 | 25 | NULL | NULL |
| 2 | 2 | 20 | 2 | 15 | NULL | NULL | NULL | NULL |
+------+------+------+------+------+------+------+------+------+
3 rows in set (0.00 sec)
When we aggregate
select i.id,
count(*) as rows,
sum(ii.qty) as inventory,
sum(ic.qty) as consumed,
sum(id.qty) as damaged,
sum(iu.qty) as unaccounted
from items i
left join items_inventory ii on ii.itemid = i.id
left join items_consumed ic on ic.itemid = i.id
left join items_damaged id on id.itemid = i.id
left join items_unaccounted iu on iu.itemid = i.id
group by i.id;
we get 'doubling' up of consumed and damaged.
+------+------+-----------+----------+---------+-------------+
| id | rows | inventory | consumed | damaged | unaccounted |
+------+------+-----------+----------+---------+-------------+
| 1 | 2 | 20 | 10 | 50 | NULL |
| 2 | 1 | 20 | 15 | NULL | NULL |
+------+------+-----------+----------+---------+-------------+
2 rows in set (0.00 sec)
One way to deal with this is to aggregate BEFORE you join by pushing the aggregations into sub queries which you would then join. For example
select i.id, ii.inventory,ic.consumed,id.damaged,iu.unaccounted,
coalesce(ii.inventory,0)+coalesce(ic.consumed,0)+coalesce(id.damaged,0)+coalesce(iu.unaccounted,0) total
from items i
left join (select ii.itemid,sum(ii.qty) as inventory from items_inventory ii group by itemid) ii on ii.itemid = i.id
left join (select ic.itemid,sum(ic.qty) as consumed from items_consumed ic group by itemid) ic on ic.itemid = i.id
left join (select id.itemid,sum(id.qty) as damaged from items_damaged id group by itemid) id on id.itemid = i.id
left join (select iu.itemid,sum(iu.qty) as unaccounted from items_unaccounted iu group by itemid) iu on iu.itemid = i.id
;
+------+-----------+----------+---------+-------------+-------+
| id | inventory | consumed | damaged | unaccounted | total |
+------+-----------+----------+---------+-------------+-------+
| 1 | 20 | 5 | 25 | NULL | 50 |
| 2 | 20 | 15 | NULL | NULL | 35 |
+------+-----------+----------+---------+-------------+-------+
2 rows in set (0.00 sec)
Working Query thanks to sir #P.Salmon
SELECT I.ItemID,
I.Item,
COALESCE(II.InventoryPrevBal,0) - COALESCE(ICP.ConsumedPrevBal,0) - COALESCE(IDP.DamagedPrevBal,0) - COALESCE(IUP.UnaccountedPrevBal,0) PrevBalance,
COALESCE(II.InventoryBal,0) CurrentDelivered,
COALESCE(IC.Consumed,0) CurrentConsumed,
COALESCE(ID.Damaged,0) CurrentDamaged,
COALESCE(IU.Unaccounted,0) CurrentUnaccounted,
COALESCE(II.InventoryPrevBal,0) + COALESCE(II.InventoryBal,0) - COALESCE(ICP.ConsumedPrevBal,0) - COALESCE(IDP.DamagedPrevBal,0) - COALESCE(IUP.UnaccountedPrevBal,0) - COALESCE(IC.Consumed,0) - COALESCE(ID.Damaged,0) - COALESCE(IU.Unaccounted,0) CurrentTotal
FROM items I
LEFT JOIN (SELECT II.ItemID, SUM(CASE WHEN DATE(II.ItemTransactionDate) < CURDATE() THEN II.Quantity ELSE 0 END) as InventoryPrevBal, SUM(CASE WHEN DATE(II.ItemTransactionDate) = CURDATE() THEN II.Quantity ELSE 0 END) as InventoryBal FROM inventory II GROUP BY ItemID) II ON II.ItemID = I.ItemID
LEFT JOIN (SELECT ICP.ItemID, ICP.TransactionDate, SUM(ICP.Quantity) as ConsumedPrevBal FROM consumeditemmonitoring ICP WHERE DATE(ICP.TransactionDate) < CURDATE() GROUP BY ItemID) ICP ON ICP.ItemID = I.ItemID
LEFT JOIN (SELECT IDP.ItemID, IDP.ItemTransactionDate, SUM(IDP.Quantity) as DamagedPrevBal FROM damagedinventory IDP WHERE DATE(IDP.ItemTransactionDate) < CURDATE() GROUP BY ItemID) IDP ON IDP.ItemID = I.ItemID
LEFT JOIN (SELECT IUP.ItemID, IUP.ItemTransactionDate, SUM(IUP.Quantity) as UnaccountedPrevBal FROM unaccounteditems IUP WHERE DATE(IUP.ItemTransactionDate) < CURDATE() GROUP BY ItemID) IUP ON IUP.ItemID = I.ItemID
LEFT JOIN (SELECT IC.ItemID, IC.TransactionDate, SUM(IC.Quantity) as Consumed FROM consumeditemmonitoring IC WHERE DATE(IC.TransactionDate) = CURDATE() GROUP BY ItemID) IC ON IC.ItemID = I.ItemID
LEFT JOIN (SELECT ID.ItemID, ID.ItemTransactionDate, SUM(ID.Quantity) as Damaged FROM damagedinventory ID WHERE DATE(ID.ItemTransactionDate) = CURDATE() GROUP BY ItemID) ID ON ID.ItemID = I.ItemID
LEFT JOIN (SELECT IU.ItemID, IU.ItemTransactionDate, SUM(IU.Quantity) as Unaccounted FROM unaccounteditems IU WHERE DATE(IU.ItemTransactionDate) = CURDATE() GROUP BY ItemID) IU ON IU.ItemID = I.ItemID
ORDER BY I.Item ASC

MySQL: select row only where closest to date has column value

I want to return all rows that were public in May (2019-05), so if a row was turned to draft (and not back to public) at any point before the end of May, I don't want it. For example:
id | post_id | status | date
-------------------------
1 | 1 | draft | 2019-03-25
2 | 1 | public | 2019-04-02
3 | 1 | draft | 2019-05-25
4 | 2 | draft | 2019-03-10
5 | 2 | public | 2019-04-01
6 | 2 | draft | 2019-06-01
The desired result for the above would return post_id 2 because its last status change prior to the end of May was to public.
post_id 1 was put back in draft before the end of May, so it would not be included.
I'm not sure how to use the correct join or sub-queries to do this as efficiently as possible.
You seem to want the status as of 2019-05-31. A correlated subquery seems like the simplest solution:
select t.*
from t
where t.date = (select max(t2.date)
from t t2
where t2.post_id = t.post_id and
t2.date <= '2019-05-31'
);
To get the ones that are public, just add a WHERE condition:
select t.*
from t
where t.date = (select max(t2.date)
from t t2
where t2.post_id = t.post_id and
t2.date <= '2019-05-31'
) and
t.status = 'public';
For performance, you want an index on (post_id, date).
You can also phrase this using a JOIN:
select t.*
from t join
(select t2.post_id, max(t2.date) as max_date
from t t2
where t2.date <= '2019-05-31'
group by t2.post_id
) t2
on t2.max_date = t.date
where t.status = 'public';
I would expect the correlated subquery to have better performance with the right indexes. However, sometimes MySQL surprises me.
we need to determine whether
the status of each post_id is public prior to the month May (the subquery with max(date)),
any post_id exists with status not equals public within the month May,
and then exclude the post_id satisfying the matter 2.
So, you can use :
select distinct t1.post_id
from tab t1
where t1.post_id not in
(
select distinct t1.post_id
from tab t1
join
(
select post_id, max(date) as date
from tab
where '2019-05-01'> date
group by post_id ) t2
on t1.post_id = t2.post_id
where t1.status != 'public'
and t1.date < '2019-06-01'
and t1.date > '2019-04-30'
);
+---------+
| POST_ID |
+---------+
| 2 |
+---------+
Demo

Query on a table to get 2 counts in a single query.

I have two tables in MySQL
table1(Date(full_date), app_id, type(free, paid))
table2(Date_fk, Year, month, day, quater)
Query for Single Count is :
select Year, count(*)
from Table1, Table2
where Table1.Date = Table2.Date and Table1.Type='Free'
GROUP BY YEAR
---------------------
| year | free_count |
---------------------
| 2019 | 10 |
---------------------
I want output as
---------------------------------
| year | free_count | Paid_count |
----------------------------------
| 2019 | 10 | 12 |
----------------------------------
Here's one option using conditional aggregation:
select year,
count(case when t1.type='free' then 1 end) as freecount,
count(case when t1.type='paid' then 1 end) as paidcount
from table1 t1
join table2 t2 on t1.date = t2.date
group by year
Also please take a look at the join syntax. In general, I'd highly recommend not using commas in your from clause.
Try this out:
SELECT
d.year,
SUM(CASE WHEN a.Type = 'Free' THEN 1 ELSE 0 END) AS free_count,
SUM(CASE WHEN a.Type = 'Paid' THEN 1 ELSE 0 END) AS paid_count
FROM Table2 d -- Dates table
LEFT JOIN Table1 a -- Apps table
ON d.Date_fk = a.Date
GROUP BY d.year;
The LEFT JOIN guarantees that you'll still get results for those years without any apps.

how to make a SELECT that merges duplicated Primary Keys

I'm performing a query that looks like this:
SELECT a.transactionID,a.customerID,b.value
FROM adjustments a
INNER JOIN change b
on a.transactionID = b.transactionID
and a.event_date = b.event_date
and a.event_id = b.event_id
WHERE comment LIKE 'TRANSFER'
ORDER BY a.transactionID;
this query brings the following result:
transactionID | customerID | value
------------------------------------
TRANSFER-001 | CUSTA | -200
TRANSFER-001 | CUSTB | 200
TRANSFER-002 | CUSTC | -150
TRANSFER-002 | CUSTD | 0
TRANSFER-003 | CUSTA | 0
TRANSFER-003 | CUSTC | 150
I need to change this query to bring a list that ignore those cases where the sum of value is 0 for the same transactionID and also, group the customerID and values as following:
transactionID | customerID_A | value_A | customerID_B | value_B
------------------------------------------------------------------
TRANSFER-002 | CUSTC | -150 | CUSTD | 0
TRANSFER-003 | CUSTA | 0 | CUSTC | 150
Can you give any advise about how to solve this?
Try this :
SELECT * FROM (
SELECT a.transactionID,a.customerID,b.value
FROM adjustments a
INNER JOIN change b
on a.transactionID = b.transactionID
and a.event_date = b.event_date
and a.event_id = b.event_id
WHERE comment LIKE 'TRANSFER'
)M
GROUP BY transactionID,customerID,value
HAVING SUM(value) <> 0
ORDER BY transactionID;
SELECT distinct a.transactionID,a.customerID,b.value
FROM adjustments a
INNER JOIN change b
on a.transactionID = b.transactionID
and a.event_date = b.event_date
and a.event_id = b.event_id
WHERE comment LIKE 'TRANSFER'
ORDER BY a.transactionID;
Try distinct at the beginning it will eliminate all the duplicate values.
If I understand correctly, you want conditional aggregation. However, you need to pivot the customers and there is no column for doing that. You can enumerate the customers for each transaction using variables, and use that to pivot the first two customers on the transaction:
SELECT transactionId,
MAX(CASE WHEN seqnum = 1 THEN customerId END) as customer_A,
MAX(CASE WHEN seqnum = 1 THEN value END) as value_A,
MAX(CASE WHEN seqnum = 2 THEN customerId END) as customer_B,
MAX(CASE WHEN seqnum = 2 THEN value END) as value_B
FROM (SELECT a.transactionID, a.customerID, b.value,
(#rn := if(#t = a.transactionID, #rn + 1,
if(#t := a.transactionID, 1, 1)
)
) as seqnum
FROM adjustments a INNER JOIN
change c
ON a.transactionID = c.transactionID AND
a.event_date = c.event_date AND
a.event_id = c.event_id CROSS JOIN
(SELECT #rn := 0, #t := '') params,
WHERE comment LIKE 'TRANSFER'
ORDER BY a.transactionID, b.value DESC
) t
GROUP BY transactionId
HAVING SUM(value) <> 0;

MySQL - count in same table

I have the following table:
+----+---------------------+---------------+
| id | created_at | deklaracja_id |
+----+---------------------+---------------+
| 1 | 2015-01-09 12:14:00 | 1 |/*deklaracja*/
| 2 | 2015-02-09 12:14:00 | 1 |/*korekta for 1*/
| 3 | 2015-03-09 12:14:00 | 3 |/*deklaracja/
| 4 | 2015-01-09 12:14:00 | 3 |/*korekta for 3*/
| 5 | 2015-10-09 12:14:00 | 3 |/*korekta for 3*/
| 6 | 2015-10-09 12:14:00 | 6 |/*deklaracja*/
+----+---------------------+---------------+
Cond:
id = deklaracja_id is "deklaracja"
id <> deklaracja_id is "korekta"
I need a query to show all "deklaracja" and count of their "korekty" later than 2015-01-09.
Ex.
+----+---------------------+---------------+
| id | created_at | korekty_count |
+----+---------------------+---------------+
| 1 | 2015-01-09 12:14:00 | 1 |
| 3 | 2015-03-09 12:14:00 | 2 |
| 6 | 2015-10-09 12:14:00 | 0 |
+----+---------------------+---------------+
I've tried something like:
SELECT *,
SUM(CASE WHEN (id <> deklaracja_id)THEN 1 ELSE 0 END )
AS korekty_count
FROM Deklaracja
WHERE created >= '2015-09-01 00:00:00'
but it's not working and now I'm totally stuck :/
You can use a correlated sub-query:
SELECT id, created_at,
(SELECT COUNT(*)
FROM Deklaracja AS t2
WHERE t1.id = t2.deklaracja_id AND
t2.id <> t2.deklaracja_id) AS AS korekty_count
FROM Deklaracja AS t1
WHERE id = deklaracja_id
Demo here
Add a GROUP BY clause to your query.
SELECT *,
SUM(CASE WHEN (id <> deklaracja_id)THEN 1 ELSE 0 END )
AS korekty_count
FROM Deklaracja
WHERE created_at >= '2015-01-09 00:00:00' GROUP BY deklaracja_id
SELECT created_at, count(*)-1
from Deklaracje d
where id in (select id from Deklaracje e
where e.deklaracja_id=d.deklaracja_id)
group by deklaracja_id
SQL Fiddle link
I would use a subquery to get the records you're interested in before grouping:
SELECT
id,
created_at,
COUNT(deklaracja_id) AS korekty_count
FROM (
SELECT id, deklaracja_id, created_at
FROM Deklaracja
WHERE created_at >= '2015-09-01 00:00:00'
AND id <> deklaracja_id
) tmp
GROUP BY id;
See demo.
Something like this:
select id, created_at,
( select count(*) from deklaracja dt
where dt.deklaracja_id <> dt.id
and dt.deklaracja_id = d.deklaracja_id
and dt.created_at >= '2015-09-01 00:00:00' ) as korekty_count
from deklaracja d
where id = deklaracja_id
The answer by Giorgos Betsos is a good one. However, if you want to achieve the same results without a sub-query like you tried in your query, then try this one using joins:
SELECT t1.id, t1.created_at, COUNT(t2.id) AS korekty_count
FROM Deklaracja AS t1
LEFT JOIN Deklaracja AS t2 ON t1.id = t2.deklaracja_id
AND t2.id <> t2.deklaracja_id
WHERE t1.id = t1.deklaracja_id
AND t1.created_at >= '2015-09-01 00:00:00'
GROUP BY t1.id
Here is a fiddle