How make request more readable and scalable? - mysql

i have request:
SELECT user_id FROM merchant_data
WHERE user_id IN (
SELECT user_id FROM merchant_data
WHERE merchant_id = 1134
AND created_date = '2022-12-02'
GROUP BY user_id
HAVING COUNT(*) > 2)
AND merchant_id = 1167
AND created_date = '2022-12-02'
GROUP BY user_id
HAVING COUNT(*) = 2;
That request return me data from something like log table. In this case i need to get all users that have 2 more rows with merchant_id == 1134 and 2 rows merchant_id == 1167. But how make it for 4 or 5 or 6 condition like merchant_id == ...?

SELECT user_id FROM merchant_data
WHERE created_date = '2022-12-02'
AND merchant_id IN (1134, 1167, 1186, ...)
GROUP BY user_id
HAVING SUM(merchant_id = 1134) >= 2
AND SUM(merchant_id = 1167) >= 2
AND SUM(merchant_id = 1186) >= 2
AND ...
That depends on an odd MySQL feature that booleans are literally the integer values 1 for true and 0 for false, so you can SUM() a boolean expression. You can't do that in standard SQL.
You could make it more standard SQL by using CASE expressions with no ELSE clause. CASE returns NULL if there is no match, and COUNT() will ignore NULLs.
SELECT user_id FROM merchant_data
WHERE created_date = '2022-12-02'
AND merchant_id IN (1134, 1167, 1186, ...)
GROUP BY user_id
HAVING COUNT(CASE merchant_id WHEN 1134 THEN 1 END) >= 2
AND COUNT(CASE merchant_id WHEN 1167 THEN 1 END) >= 2
AND COUNT(CASE merchant_id WHEN 1186 THEN 1 END) >= 2
AND ...

Related

sql query issue in record

I am getting problem in record using following query
SELECT user_id FROM temp
WHERE
(value = 1 AND field_id = 11) AND
value = 1 AND field_id = 12
Here is the table.
I should get record of 101 user_id.
Any one idea on this?
You need to aggregate by user:
SELECT user_id
FROM temp
GROUP BY user_id
HAVING
SUM(CASE WHEN value = 1 AND field_id = 11 THEN 1 ELSE 0 END) > 0 AND
SUM(CASE WHEN value = 1 AND field_id = 12 THEN 1 ELSE 0 END) > 0;
try this sql select DISTINCT user_id from temp where value=1 and (field_id=12 or field_id=11)
You can also use where clause :
select user_id
from table t
where value = 1 and field_id in (11, 12)
group by user_id
having count(distinct field_id) = 2;

How to split SQL query results into columns based on two WHERE conditions and two calculated COUNT fields?

I have the following (simplified) database schema:
Persons:
[Id] [Name]
-------------------
1 'Peter'
2 'John'
3 'Anna'
Items:
[Id] [ItemName] [ItemStatus]
-------------------
10 'Cake' 1
20 'Dog' 2
ItemDocuments:
[Id] [ItemId] [DocumentName] [Date]
-------------------
101 10 'CakeDocument1' '2016-01-01 00:00:00'
201 20 'DogDocument1' '2016-02-02 00:00:00'
301 10 'CakeDocument2' '2016-03-03 00:00:00'
401 20 'DogDocument2' '2016-04-04 00:00:00'
DocumentProcessors:
[PersonId] [DocumentId]
-------------------
1 101
1 201
2 301
I have also set up an SQL fiddle to play with: http://www.sqlfiddle.com/#!3/e6082
The relation logic is the following: every Person can work on zero or infinite number of ItemDocuments (many-to-many); each ItemDocument belongs to exactly one Item (one-to-many). Item has status 1 - Active, 2 - Closed
What I need is a report that fulfills the following requirements:
for each person in Persons table, display count of Items that have ItemDocuments related to this person
the counts should be split in two columns by ItemStatus
the query should be filterable by two optional date periods (using two BETWEEN conditions on ItemDocuments.Date field) and the Item counts should also be split into two periods
if a Person does not have any ItemDocuments assigned, it still should be shown in the results with all count values set to 0
if a Person has more than one ItemDocument for an Item, the Item still should be counted only once
Essentially, here is how the results should look like if I use both periods to NULL (to read all the data):
[PersonName] [Active Items for period 1] [Closed Items for period 1] [Active Items for period 2] [Closed Items for period 2]
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
'Peter' 1 1 1 1
'John' 1 0 1 0
'Anna' 0 0 0 0
While I can create an SQL query for each requirement separately, I have a problem to understand how to combine all of them together into one.
For example, I can split ItemStatus counts in two columns using
COUNT(CASE WHEN t.ItemStatus = 1 THEN 1 ELSE NULL END) AS Active,
COUNT(CASE WHEN t.ItemStatus = 2 THEN 1 ELSE NULL END) AS Closed
and I can filter by two periods (with max/min date constants from MS SQL server specification to avoid NULLs for optional period dates) using
between coalesce(#start1, '1753-01-01') and coalesce(#end1, '9999-12-31')
between coalesce(#start2, '1753-01-01') and coalesce(#end2, '9999-12-31')
but how to combine all of this together, considering also JOINs between tables?
Is there any technique, join or MS SQL Server specific approach to do this in efficient way?
My first attempt seems to work as required but it looks like ugly subquery duplications multiple times:
DECLARE #start1 DATETIME, #start2 DATETIME, #end1 DATETIME, #end2 DATETIME
-- SET #start2 = '2017-01-01'
SELECT
p.Name,
(SELECT COUNT(1)
FROM Items i
WHERE i.ItemStatus = 1 AND EXISTS(
SELECT 1
FROM DocumentProcessors AS dcp
INNER JOIN ItemDocuments AS idc ON dcp.DocumentId = idc.Id
WHERE dcp.PersonId = p.Id AND idc.ItemId = i.Id
AND idc.Date BETWEEN COALESCE(#start1, '1753-01-01') AND COALESCE(#end1, '9999-12-31')
)
) AS Active1,
(SELECT COUNT(*)
FROM Items i
WHERE i.ItemStatus = 2 AND EXISTS(
SELECT 1
FROM DocumentProcessors AS dcp
INNER JOIN ItemDocuments AS idc ON dcp.DocumentId = idc.Id
WHERE dcp.PersonId = p.Id AND idc.ItemId = i.Id
AND idc.Date BETWEEN COALESCE(#start1, '1753-01-01') AND COALESCE(#end1, '9999-12-31')
)
) AS Closed1,
(SELECT COUNT(1)
FROM Items i
WHERE i.ItemStatus = 1 AND EXISTS(
SELECT 1
FROM DocumentProcessors AS dcp
INNER JOIN ItemDocuments AS idc ON dcp.DocumentId = idc.Id
WHERE dcp.PersonId = p.Id AND idc.ItemId = i.Id
AND idc.Date BETWEEN COALESCE(#start2, '1753-01-01') AND COALESCE(#end2, '9999-12-31')
)
) AS Active2,
(SELECT COUNT(*)
FROM Items i
WHERE i.ItemStatus = 2 AND EXISTS(
SELECT 1
FROM DocumentProcessors AS dcp
INNER JOIN ItemDocuments AS idc ON dcp.DocumentId = idc.Id
WHERE dcp.PersonId = p.Id AND idc.ItemId = i.Id
AND idc.Date BETWEEN COALESCE(#start2, '1753-01-01') AND COALESCE(#end2, '9999-12-31')
)
) AS Closed2
FROM Persons p
I'm not absolutely sure if I really got what you want, but you might try this
WITH AllData AS
(
SELECT p.Id AS PersonId
,p.Name AS Person
,id.Date AS DocDate
,id.DocumentName AS DocName
,i.ItemName AS ItemName
,i.ItemStatus AS ItemStatus
,CASE WHEN id.Date BETWEEN COALESCE(#start1, '1753-01-01') AND COALESCE(#end1, '9999-12-31') THEN 1 ELSE 0 END AS InPeriod1
,CASE WHEN id.Date BETWEEN COALESCE(#start2, '1753-01-01') AND COALESCE(#end2, '9999-12-31') THEN 1 ELSE 0 END AS InPeriod2
FROM Persons AS p
LEFT JOIN DocumentProcessors AS dp ON p.Id=dp.PersonId
LEFT JOIN ItemDocuments AS id ON dp.DocumentId=id.Id
LEFT JOIN Items AS i ON id.ItemId=i.Id
)
SELECT PersonID
,Person
,COUNT(CASE WHEN ItemStatus = 1 AND InPeriod1 = 1 THEN 1 ELSE NULL END) AS ActiveIn1
,COUNT(CASE WHEN ItemStatus = 2 AND InPeriod1 = 1 THEN 1 ELSE NULL END) AS ClosedIn1
,COUNT(CASE WHEN ItemStatus = 1 AND InPeriod2 = 1 THEN 1 ELSE NULL END) AS ActiveIn2
,COUNT(CASE WHEN ItemStatus = 2 AND InPeriod2 = 1 THEN 1 ELSE NULL END) AS ClosedIn2
FROM AllData
GROUP BY PersonID,Person

MySQL CASE with GROUP BY count

I have these three queries
SELECT COUNT(question_id) AS correct_count, uID FROM mytable WHERE id
>= 0 AND id <= 1000 AND correct = 1 AND answer_id IS NOT NULL GROUP BY user_id
SELECT COUNT(question_id) AS incorrect_count, uID FROM mytable WHERE id >= 0 AND id <= 1000 AND correct !=1 AND answer_id IS NOT NULL GROUP BY user_id
SELECT COUNT(question_id) AS null_count, uID FROM mytable WHERE id >= 0 AND id <= 1000 AND answer_id IS NULL GROUP BY user_id
How can I join this as a single query using CASE or IF ?
SELECT user_id,
sum(id >= 0 AND id <= 1000 AND correct = 1 AND answer_id IS NOT NULL) AS correctCnt,
sum(id >= 0 AND id <= 1000 AND correct !=1 AND answer_id IS NOT NULL) AS incorrectCnt,
sum(id >= 0 AND id <= 1000 AND answer_id IS NULL) as nullCnt
FROM mytable
GROUP BY user_id
count only counts non-null items, so you could convert the where clauses to case expressions that return null when they do not match:
SELECT user_id,
COUNT (CASE WHEN id >= 0 AND id <= 1000 AND correct = 1 AND answer_id IS NOT NULL THEN 1 ELSE NULL) AS correct_count,
COUNT (CASE WHEN id >= 0 AND id <= 1000 AND correct !=1 AND answer_id IS NOT NULL THEN 1 ELSE NULL) AS incorrect_count,
COUNT (CASE WHEN WHERE id >= 0 AND id <= 1000 AND answer_id IS NULL) AS null_count
FROM mytable
GROUP BY user_id
Note that all these case statements share some conditions, so those could be extracted to a where clause:
SELECT user_id,
COUNT (CASE WHEN correct = 1 AND answer_id IS NOT NULL THEN 1 ELSE NULL) AS correct_count,
COUNT (CASE WHEN correct !=1 AND answer_id IS NOT NULL THEN 1 ELSE NULL) AS incorrect_count,
COUNT (CASE WHEN WHERE answer_id IS NULL THEN 1 ELSE NULL) AS null_count
FROM mytable
WHERE id >= 0 AND id <= 1000
GROUP BY user_id

how to marge the values of column in mysql

Lets consider this query
select class_id,case when event_id=2 then sum(time_spent) end as timespent ,case when event_id=3 then sum(timespent) end as visitedtimespent from class group by class_id,event_id;
output is looking like
class_id timespent visitedtimespent
1 2000 NULL
1 NULL 10
2 4000 NULL
2 NULL 5
when I use this query
select class_id,case when event_id=2 then sum(time_spent) end as timespent ,case when event_id=3 then sum(time_spent) end as timespent from class group by class_id;
output is looking like
class_id timespent visitedtimespent
1 2000 NULL
2 4000 NULL
but I expected this output
class_id timespent visitedtimespent
1 2000 10
2 4000 5
how can I achieve this?
select class_id,
sum(case when event_id=2 then time_spent else 0 end) as timespent,
sum(case when event_id=3 then time_spent else 0 end) as visitedtimespent
from class
group by class_id
sum the case.
select class_id,
sum(case when event_id=2 then time_spent end) as timespent ,
sum(case when event_id=3 then time_spent end) as visitedtimespent
from class group by class_id;
to explain the difference:
case when id... then sum(value) is equivalent to
select case when id then value from
(
select id, sum(value) as value from table
)subquery
which is an illegal grouping(ID is not aggregated or included in grouping, so the ID value will be chosen at random between all existing entries), and your ID information will be lost. IF you then apply a case to the ID info, you will not get relevant results.

SQL QUERY- multiple COUNT returns wrong (same) results when using in GROUP BY

I will simplfy this:
I have two SQL expressions which works OK:
First:
select count(*) as number1
from T1
where DATE1>'2012-01-01' and DATE2<'2012-12-31'
Result:13
select count(*) as number2
from T1
where DATE3>DATE2 and CURDATE()>DATE2
Result:5
But when I try to insert those two COUNTS in GROUP BY I am getting as resulyt always 13!!!
SELECT NAME,
COUNT(case when DATE1>'2012-01-01' and DATE2<'2012-12-31' then 1 else 0 end) as number1,
COUNT (case when (DATE3>DATE2 and CURDATE()>DATE2) then 1 else 0 end) as number 2
from T1
I am getting as result:
NAME NUMBER1 NUMBER2
A 5 5
B 4 4
C 4 4
But I should get:
NAME NUMBER1 NUMBER2
A 5 4
B 4 0
C 4 1
So that SUM of columns be 13 and 5 like in first two queries . What am I doing wrong?
Thank you
COUNT(expression) counts not null expressions. You can modify your query by changing the ELSE 0 to ELSE NULL or by removing it (the ELSE NULL is implied at CASE expressions):
SELECT name,
COUNT(CASE WHEN date1 > '2012-01-01' AND date2 < '2012-12-31'
THEN 1 END
) AS number1,
COUNT(CASE WHEN date3 > date2 AND CURDATE() > date2
THEN 1 END
) AS number2
FROM T1
GROUP BY name ;
Do not use count here.Use Sum. If you want to use the count then put null in else instead of 0
try:
SELECT NAME,
(select count(*) from T1 where DATE1>'2012-01-01' and DATE2<'2012-12-31') AS number1,
(select count(*) from T1 where DATE3>DATE2 and CURDATE()>DATE2) AS number2
FROM T1
GROUP BY NAME
Try this:
SELECT name,
SUM(CASE WHEN DATE1>'2012-01-01' AND DATE2<'2012-12-31' THEN 1 ELSE 0 END) AS number1,
SUM(CASE WHEN (DATE3>DATE2 AND CURDATE()>DATE2) THEN 1 ELSE 0 END) AS number2
FROM T1 GROUP BY name