multiple count conditions with single query - sql

I have a table like below -
Student ID | History | Maths | Geography
1 A B B
2 C C E
3 D A B
4 E D A
How to find out how many students got A in history, B in maths and E in Geography with a single sql query ?

If you want to get number of students who got A in History in one column, number of students who got B in Maths in second column and number of students who got E in Geography in third then:
select
sum(case when [History] = 'A' then 1 else 0 end) as HistoryA,
sum(case when [Maths] = 'B' then 1 else 0 end) as MathsB,
sum(case when [Geography] = 'E' then 1 else 0 end) as GeographyC
from Table1
If you want to count students who got A in history, B in maths and E in Geography:
select count(*)
from Table1
where [History] = 'A' and [Maths] = 'B' and [Geography] = 'E'

If you want independent counts use:
SELECT SUM(CASE WHEN Condition1 THEN 1 ELSE 0 END) AS 'Condition1'
,SUM(CASE WHEN Condition2 THEN 1 ELSE 0 END) AS 'Condition2'
,SUM(CASE WHEN Condition3 THEN 1 ELSE 0 END) AS 'Condition3'
FROM YourTable
If you want multiple conditions for one count use:
SELECT COUNT(*)
FROM YourTable
WHERE Condition1
AND Condition2
AND Condition3
It sounds like you want multiple independent counts:
SELECT SUM(CASE WHEN History = 'A' THEN 1 ELSE 0 END) AS 'History A'
,SUM(CASE WHEN Maths = 'B' THEN 1 ELSE 0 END) AS 'Maths B'
,SUM(CASE WHEN Geography = 'E' THEN 1 ELSE 0 END) AS 'Geography E'
FROM YourTable

You can try to select from multiple select statements
SELECT t1.*, t2.*, t3.* FROM
(SELECT COUNT(*) AS h FROM students WHERE History = 'A') as t1,
(SELECT COUNT(*) AS m FROM students WHERE Maths = 'B') as t2,
(SELECT COUNT(*) AS g FROM students WHERE Geography = 'E') as t3

Related

Flag=1/0 based on multiple criteria on same column

I have a temp table that is being created, we will say that column 1 is YearMonth, column2 as user_id, Column 3 is Type.
YearMonth User_id Type
200101 1 x
200101 2 y
200101 2 z
200102 1 x
200103 2 x
200103 2 p
200103 2 q
I want to count userids based on flag based on type. Hence I am trying to set flag to 1 and 0 but it always results in 0.
So for e.g. when the type contains x or y or z AND type contains P or Q then flag=1 by YearMonth.
I am trying something like
SELECT count (distinct t1.user_id) as count,
t1.YearMonth,
case when t1.type in ('x','y','z')
and
t1.type in ('p','q') then 1 else 0 end as flag
FROM table t1
group by 2,3;
I would like to know why it doesn't give output as below:
count YearMonth Flag
0 200001 1
2 200001 0
1 200002 1
1 200002 0
What am I missing here? Thanks
If I follow you correctly, you can use two levels of aggregation:
select yearmonth, flag, count(*) cnt
from (
select yearmonth, id,
case when max(case when t1.type in ('x', 'y', 'z') then 1 else 0 end) = 1
and max(case when t1.type in ('p', 'q') then 1 else 0 end) = 1
then 1
else 0
end as flag
from mytable
group by yearmonth, id
) t
group by yearmonth, flag
This first flags users for each month, using conditional aggregation, then aggregates by flag and month.
If you also want to display 0 for flags that do not appear for a given month, then you can generate the combinations with a cross join first, then brin the above resultset with a left join:
select y.yearmonth, f.flag, count(t.id) cnt
from (select distinct yearmonth from mytable) y
cross join (values (0), (1)) f(flag)
left join (
select yearmonth, id,
case when max(case when t1.type in ('x', 'y', 'z') then 1 else 0 end) = 1
and max(case when t1.type in ('p', 'q') then 1 else 0 end) = 1
then 1
else 0
end as flag
from mytable
group by yearmonth, id
) t on t.yearmonth = y.yearmonth and t.flag = f.flag
group by y.yearmonth, f.flag
I thought a very similar idea as GMB, however, like him, I don't get the expected results. Likely, however, we both are assuming the expected results are wrong:
SELECT COUNT(DISTINCT UserID) AS [Count],
YearMonth,
CASE WHEN COUNT(CASE WHEN [Type] IN ('x','y','z') THEN 1 END) > 0
AND COUNT(CASE WHEN [Type] IN ('p','q') THEN 1 END) > 0 THEN 1 ELSE 0
END AS Flag
FROM (VALUES(200101,1,'x'),
(200101,2,'y'),
(200101,2,'z'),
(200102,1,'x'),
(200103,2,'x'),
(200103,2,'p'),
(200103,2,'q')) V(YearMonth,UserID,[Type])
GROUP BY YearMonth;

How do I get multiple counts based on distinct parameters in one SQL query?

I have a table with multiple medical records of relatives. I'm trying to count instances of cancer diagnoses per degree of relative.
CREATE TABLE Relatives
(person varchar(9),
relative varchar(12),
degree int,
relativeID varchar(9),
age int,
CancerDiagnosis varchar(2))
INSERT INTO RELATIVES (person, relative, degree, relativeid, age, cancerdiagnosis)
VALUES ('12345678','aunt','2','54876','36','Y'),
('12345678','aunt','2','54876','43','Y'),
('12345678','cousin','3','213786','39','N'),
('12345678','daughter','1','128756','15','Y'),
('12345678','daughter','1','128756','21','Y'),
('12345678','daughter','1','128756','12','N'),
('12345678','father','1','867578','64','Y'),
('98765432','cousin','3','987645','39','Y'),
('98765432','cousin','3','987645','40','Y'),
('98765432','sibling','1','123744','22','N'),
('98765432','mother','1','876418','64','Y'),
('98765432','mother','1','876418','65','Y'),
I expect the result:
person fdr_cancer sdr_cancer tdr_cancer
12345678 2 1 0
98765432 1 0 1
Here is my query:
SELECT person,
SUM(CASE WHEN cancerdiagnosis = 'y' AND degree = 1 THEN 1 ELSE 0 END) AS
FDR_Cancer,
SUM(CASE WHEN cancerdiagnosis = 'y' AND degree = 2 THEN 1 ELSE 0 END) AS
SDR_Cancer,
sum(CASE WHEN cancerdiagnosis = 'y' AND degree = 3 THEN 1 ELSE 0 END) AS
TDR_Cancer
FROM Relatives
GROUP BY person
How do I get this to count distinct rows by relativeID, degree, and diagnosis?
If I understand correctly, you want conditional count(distinct):
SELECT person,
COUNT(DISTINCT CASE WHEN cancerdiagnosis = 'y' AND degree = 1 THEN relativeid END) AS FDR_Cancer,
COUNT(DISTINCT CASE WHEN cancerdiagnosis = 'y' AND degree = 2 THEN relativeid END) AS SDR_Cancer,
COUNT(DISTINCT CASE WHEN cancerdiagnosis = 'y' AND degree = 3 THEN relativeid END) AS TDR_Cancer
FROM Relatives
GROUP BY person;
Instead of three COUNT DISTINCT you can apply DISTINCT before aggregation, should be more efficient:
with cte as
( select distinct
person, degree, relativeid, cancerdiagnosis
from Relatives
)
SELECT person,
SUM(CASE WHEN cancerdiagnosis = 'y' AND degree = 1 THEN 1 ELSE 0 END) AS FDR_Cancer,
SUM(CASE WHEN cancerdiagnosis = 'y' AND degree = 2 THEN 1 ELSE 0 END) AS SDR_Cancer,
sum(CASE WHEN cancerdiagnosis = 'y' AND degree = 3 THEN 1 ELSE 0 END) AS TDR_Cancer
FROM cte
GROUP BY person
It might be possible to move the cancerdiagnosis = 'y' as a WHERE-condititon into the CTE (but then a person where the diagnosis is N for all rows will be omitted):
with cte as
( select distinct
person, degree, relativeid
from Relatives
where cancerdiagnosis = 'y'
)
SELECT person,
SUM(CASE WHEN degree = 1 THEN 1 ELSE 0 END) AS FDR_Cancer,
SUM(CASE WHEN degree = 2 THEN 1 ELSE 0 END) AS SDR_Cancer,
sum(CASE WHEN degree = 3 THEN 1 ELSE 0 END) AS TDR_Cancer
FROM cte
GROUP BY person
The previous answers are easier to understand, but another option is to use PIVOT
SELECT
person
,[1] AS FDR_Cancer
,[2] AS SDR_Cancer
,[3] AS TDR_Cancer
FROM
(
SELECT DISTINCT
person
,relativeid
,degree
,CancerDiagnosis
FROM Relatives
) ps
PIVOT
(
COUNT(relativeid) FOR degree IN ([1],[2],[3])
) AS pvt
WHERE
pvt.CancerDiagnosis = 'Y'

Trouble ordering GROUP BY, ORDER BY AND JOIN

i'm having trouble ordering a query.
I have this table (AttendanceLog);
ClassID | StudentPin | Status
69 1 YES
8 2 NO
10 2 NO
17 3 NO
43 5 YES
58 6 YES
and this table (Students):
STUDENTPIN | FNAME | LNAME | INTERNATIONAL
1 X X NO
2 X X YES
3 X X NO
4 X X YES
I want to find out the which INTERNATIONAL students (Fname, Lname and StudentPIN) have missed 10 or more classes (attendancelog status being no).
Currently I have this (below) which tells me the studentPIN and the number of classes attended and no attended by each student, however I am unable to join the two tables together.
SELECT
ATTENDANCELOG.studentpin,
SUM(CASE WHEN status = 'YES' THEN 1 ELSE 0 END) AS number_of_yes,
SUM(CASE WHEN status = 'NO' THEN 1 ELSE 0 END) AS number_of_no
FROM attendancelog
GROUP BY ATTENDANCELOG.studentpin
ORDER BY ATTENDANCELOG.studentpin
Thanks!
you could use a join
SELECT
ATTENDANCELOG.studentpin,
Students.FNAME,
Students.LNAME,
SUM(CASE WHEN status = 'YES' THEN 1 ELSE 0 END) AS number_of_yes,
SUM(CASE WHEN status = 'NO' THEN 1 ELSE 0 END) AS number_of_no
FROM attendancelog
INNER JOIN Students ON Students.STUDENTPIN = attendancelog.StudentPin
and INTERNATIONAL='YES'
GROUP BY ATTENDANCELOG.studentpin, Students.FNAME, Students.LNAME
ORDER BY ATTENDANCELOG.studentpin
Join on student pin, put your international = 'YES' filter in the where clause, and filter for more than 10 misses in a having clause. You can also shorten the case expressions a little:
select a.studentpin
, s.fname, s.lname, s.international
, count(case a.status when 'YES' then 1 end) as attended
, count(case a.status when 'NO' then 1 end) as missed
from attendancelog a
join students s on s.studentpin = a.studentpin
where international = 'YES'
group by s.fname, s.lname, s.international, a.studentpin
having count(case a.status when 'NO' then 1 end) > 10
order by s.fname, s.lname, a.studentpin;

SQL query rewrite for prettification and or performance improvement

I have a query that essentially amounts to:
Select query 1
Union
Select query 2
where rowid not in query 1 rowids
Is there a prettier / more performant way to do this? I'm assuming the results of query 1 would be cached and thus utilized in the union... but it's also kinda oogly.
Update with the original query:
SELECT FruitType
, count(CASE WHEN Status = 0 THEN 1 ELSE 0 END) AS Fresh
, count(CASE WHEN Status = 1 THEN 1 ELSE 0 END) AS Ripe
, count(CASE WHEN Status = 2 THEN 1 ELSE 0 END) AS Moldy
FROM FruitTypes FT1
LEfT JOIN Fruits F on F.FTID = FT1.ID
where
Fruit.IsHighPriced = 0
GROUP BY FruitType
Union ALL
select FruitType, 0 as Fresh, 0 as Ripe, 0 as Moldy
FROM FruitTypes ft3
where
ft3.StoreID = #PassedInStoreID
and FruitType NOT IN
(
SELECT FruitType
, count(CASE WHEN Status = 0 THEN 1 ELSE 0 END) AS Fresh
, count(CASE WHEN Status = 1 THEN 1 ELSE 0 END) AS Ripe
, count(CASE WHEN Status = 2 THEN 1 ELSE 0 END) AS Moldy
FROM FruitTypes FT2
LEfT JOIN Fruits F on F.FTID = FT2.ID
where
Fruit.IsHighPriced = 0
GROUP BY FruitType
)
Thanks!
You don't need the second case statement in the NOT in clause. And not Exists is often faster in SQL Server.
SELECT FruitType
, count(CASE WHEN Status = 0 THEN 1 ELSE 0 END) AS Fresh
, count(CASE WHEN Status = 1 THEN 1 ELSE 0 END) AS Ripe
, count(CASE WHEN Status = 2 THEN 1 ELSE 0 END) AS Moldy
FROM FruitTypes FT1
LEfT JOIN Fruits F on F.FTID = FT1.ID
where
Fruit.IsHighPriced = 0
GROUP BY FruitType
Union ALL
select FruitType, 0 as Fresh, 0 as Ripe, 0 as Moldy
FROM FruitTypes ft3
where
ft3.StoreID = #PassedInStoreID
and NOT EXISTS
(
SELECT *
FROM FruitTypes FT2
LEfT JOIN Fruits F on F.FTID = FT2.ID
where
Fruit.IsHighPriced = 0
and ft3.FruitType = FT2.FruitType
)
The prettiest way of writing would probably be by turning query #1 into a view or a function, then using that view or function to call the repetitious code.
Performance could possibly be improved by using query #1 to fill a temp table or table variable, then using that temp table in place of the repititious code.

Using Pivot or CTE to horizontalize a query

I am using sql 2008
My data set looks like
Entity Type1 Type2 Balance
1 A R 100
1 B Z 200
1 C R 300
2 A X 1000
2 B Y 2000
My output should look like
Entity A-Type2 A-Balance B-Type2 B-Balance C-Type2 C-Balance
1 R 100 Z 200 R 300
2 X 1000 Y 2000 0
Now I started writing a pivot query, and I think I can get away with MAX because there should be one record per Entity/Type1 combination. But can not figure out how to do two fields in one pivot. Is this possible? Is this something that CTE could help out with?
Easiest is the MAX idea, but with a CASE statement, e.g.:
SELECT
Entity,
MAX(CASE WHEN Type1 = 'A' THEN Type2 ELSE NULL END) AS AType2,
MAX(CASE WHEN Type1 = 'A' THEN Balance ELSE NULL END) AS ABalance,
MAX(CASE WHEN Type1 = 'B' THEN Type2 ELSE NULL END) AS BType2,
MAX(CASE WHEN Type1 = 'B' THEN Balance ELSE NULL END) AS BBalance,
MAX(CASE WHEN Type1 = 'C' THEN Type2 ELSE NULL END) AS CType2,
MAX(CASE WHEN Type1 = 'C' THEN Balance ELSE NULL END) AS CBalance
FROM
...
GROUP BY
Entity
In other words, only use the value when Type1 is a specific value (with other Type1 values getting a null).
You just use conditional aggregation for the pivoting like this:
select Entity,
max(case when Type1 = 'A' then Type2 end) as A_Type2,
max(case when Type1 = 'A' then Balance else 0 end) as A_Balance,
max(case when Type1 = 'B' then Type2 end) as B_Type2,
max(case when Type1 = 'B' then Balance else 0 end) as B_Balance,
max(case when Type1 = 'C' then Type2 end) as C_Type2,
max(case when Type1 = 'C' then Balance else 0 end) as C_Balance
from MyDataSet mds
group by Entity;
Here's doing it with a pivot and a lookup.
SELECT
data.Entity,
ISNULL(a.Type2,'') AS [A-Type2],
ISNULL([A-Balance],0) AS [A-Balance],
ISNULL(b.Type2,'') AS [B-Type2],
ISNULL([B-Balance],0) AS [B-Balance],
ISNULL(c.Type2,'') AS [C-Type2],
ISNULL([C-Balance],0) AS [C-Balance]
FROM
(
SELECT
Entity,
A AS [A-Balance],
B AS [B-Balance],
C AS [C-Balance]
FROM
(
SELECT Entity, Type1, Balance FROM #table
) t
PIVOT (
MAX(Balance)
FOR Type1 IN ([A],[B],[C])
) piv
) data
LEFT OUTER JOIN #table a on a.Type1 = 'A'
AND a.Entity = data.Entity AND a.Balance = [A-Balance]
LEFT OUTER JOIN #table b on b.Type1 = 'B'
AND b.Entity = data.Entity AND b.Balance = [B-Balance]
LEFT OUTER JOIN #table c on c.Type1 = 'C'
AND c.Entity = data.Entity AND c.Balance = [C-Balance]