SQL: SUM OR COUNT with CASE WHEN condition in multiple criteria - sql

Course name
Section number
Course type
MATH 101
1
In person
MATH 101
2
In person
MATH 101
3
Online
MATH 101
4
In person
SOC 101
1
In person
SOC 101
2
In person
SOC 101
3
In person
ENGL 201
1
In person
ENGL 201
2
Online
ENGL 201
3
Online
ENGL 201
4
In person
PHY 101
1
Online
PHY 101
2
Online
From this table, I'd like to count Courses with only an 'In person' course, an 'Online' course, and both course types.
The query I tried is below.
SELECT
SUM(CASE WHEN coursetype = 'Inperson' AND coursetype = 'Online' THEN 1 ELSE 0 END) AS bothtype,
SUM(CASE WHEN coursetype = 'Online' THEN 1 ELSE 0 END) AS Onlineonly,
SUM(CASE WHEN coursetype = 'Inperson' THEN 1 ELSE 0 END) AS Onlineonly
From Course
The result what I expected is
bothtpye
Onlineonly
Inpersononly
2
1
1
but I got
bothtpye
Onlineonly
Inpersononly
0
7
6
Please advise me to get through this.
Thank you.

My solution uses double conditional aggregation.
SELECT SUM (CASE WHEN In_Person > 0 AND Online > 0 THEN 1 ELSE 0 END) as bothtype,
SUM (CASE WHEN In_Person > 0 AND Online = 0 THEN 1 ELSE 0 END) as inpersononly,
SUM (CASE WHEN In_Person = 0 AND Online > 0 THEN 1 ELSE 0 END) as onlineonly
FROM (
SELECT Course_name,
SUM(CASE WHEN Course_type='In Person' THEN 1 ELSE 0 END) as In_Person,
SUM(CASE WHEN Course_type='Online' THEN 1 ELSE 0 END) as Online
FROM Course
GROUP BY Course_name
) tot
DEMO Fiddle

SUGGESTION ( using PL/SQL ! ) :
CREATE PROCEDURE countCourses(OUT bothtype INT,OUT Inpersononly INT,OUT Onlineonly INT)
begin
SELECT COUNT(*) INTO bothtype FROM Course;
select COUNT(*) INTO Inpersononly FROM Course
WHERE courseType = "In person";
select COUNT(*) INTO Onlineonly FROM Course
WHERE courseType = "Online";
end;
call countCourses(#bothtype,#Inpersononly,#Onlineonly);
SELECT #bothtype,#Inpersononly,#Onlineonly;
EXPLICATION :
Creating procedure to store the count of each type of course in OUT variable
Call the procedure with convenient parameters
Select out given parameters

Related

SQL - Making 4 new columns in a result from another column

So, I'm making a data base for my college class, it's about a foreign languages school, and I need to ( using a single query ), have a number of people that are attending a certain language class, but it has to be seperated by the age group. For example, this is how the result table should look like:
Language | 14-25 | 25-35 | 35-50 | 50+ |
German | 1 | 0 | 0 | 0 |
Italian | 2 | 1 | 0 | 0 |
English | 5 | 0 | 0 | 0 |
I need to do this by joining the tables "Class" that has attributes (Language, Number of students), and "Student" that has attributes (ID, name, surname, age, prior knowledge ( eg. A1, B2, ... ))
So I somehow have to figure out in which age group a certain individual goes to, then if he goes there, increment the number of students for that age group by one.
You can build the sum and group the entries using CASE WHEN, so your query will look like this:
SELECT c.language,
SUM(CASE WHEN s.age BETWEEN 14 AND 25 THEN 1 ELSE 0 END) AS '14-25',
SUM(CASE WHEN s.age BETWEEN 25 AND 35 THEN 1 ELSE 0 END) AS '25-35',
SUM(CASE WHEN s.age BETWEEN 35 AND 50 THEN 1 ELSE 0 END) AS '35-50',
SUM(CASE WHEN s.age >= 50 THEN 1 ELSE 0 END) AS '50+'
FROM class c
JOIN student_class sc ON c.language = sc.class_language
JOIN student s ON s.id = sc.student_id
GROUP BY c.language;
You have to take care because as example a person whose age is 25 will be selected in both groups "15-25" and "25-35". If this is not intended, you could do something like this:
...SUM(CASE WHEN s.age BETWEEN 14 AND 25 THEN 1 ELSE 0 END) AS '14-25',
SUM(CASE WHEN s.age BETWEEN 26 AND 35 THEN 1 ELSE 0 END) AS '25-35',
SUM(CASE WHEN s.age BETWEEN 36 AND 50 THEN 1 ELSE 0 END) AS '35-50',
SUM(CASE WHEN s.age > 50 THEN 1 ELSE 0 END) AS '50+'...
Please see the working example here: db<>fiddle
You could add an ORDER BY c.language at the end if you want.
A last note: The column aliases shown here ('14-25' etc.) will not work on every DB type and might be replaced depending on DB type and personal "taste".
Assuming you have a table called something like ClassStudent which is linking the individual students to the class (which you absolutely need to fulfil this requirement)...
SELECT c.Language,
[14-25] = SUM(IIF(s.age BETWEEN 14 AND 25, 1, 0)),
[25-35] = SUM(IIF(s.age BETWEEN 25 AND 35, 1, 0)),
[35-50] = SUM(IIF(s.age BETWEEN 35 AND 50, 1, 0)),
[50+] = SUM(IIF(s.age >= 50, 1, 0)),
FROM Class c
INNER JOIN ClassStudent cs ON c.Language = cs.Language /* you need this table */
INNER JOIN Student s ON cs.StudentID = s.ID
GROUP BY c.Language
Here, IIF is like a ternary operator in SQL form, and the SUM lets you count up where the condition is met.

Group by one column and return several columns on multiple conditions - T-SQL

I have two tables which I can generate with SELECT statements (joining multiple tables) as follows:
Table 1:
ID
Site
type
time
1
Dallas
2
01-01-2021
2
Denver
1
02-01-2021
3
Chicago
1
03-01-2021
4
Chicago
2
29-11-2020
5
Denver
1
28-02-2020
6
Toronto
2
11-05-2019
Table 2:
ID
Site
collected
deposited
1
Denver
NULL
29-01-2021
2
Denver
01-04-2021
29-01-2021
3
Chicago
NULL
19-01-2020
4
Dallas
NULL
29-01-2019
5
Winnipeg
13-02-2021
17-01-2021
6
Toronto
14-02-2020
29-01-2020
I would like the result to be grouped by Site, having on each column the COUNT of type=1 , type=2, deposited and collected, all of the 4 columns between a selected time interval. Example: (interval between 01-06-2020 and 01-06-2021:
Site
type1
type2
deposited
collected
Dallas
0
1
0
0
Denver
1
0
2
1
Chicago
1
1
0
0
Toronto
0
0
0
0
Winnipeg
0
0
1
1
How about union all and aggregation?
select site,
sum(case when type = 1 then 1 else 0 end) as type_1,
sum(case when type = 2 then 1 else 0 end) as type_2,
sum(deposited) as deposited, sum(collected) as collected
from ((select site, type, 0 as deposited, 0 as collected
from table1
) union all
(select site, null,
(case when deposited is not null then 1 else 0 end),
(case when collected is not null then 1 else 0 end)
from table2
)
) t12
group by site;
Combine your tables 1 and 2 with a join on Site
Use COUNT(CASE WHEN type = 1 then 1 END) as type1 and a similar construct for type 2
Use COUNT(CASE WHEN somedate BETWEEN '2020-06-01' and '2021-06-01' then 1 END) as ... for your dates

Find count group by id in SQL Server

I need some help to solve this query. I have a table which contains the ages of the passengers who are going to stay in a room which is mentioned below:
Age RoomId
----- ---
1 1
12 1
8 1
19 1
3 2
12 2
18 2
21 3
Also, I have properties table which contains the maximum age of the child and maximum age of the infant. Based on the age of the passenger, I need to segregate them to adult, child, and infant to each of the properties.
Properties table structure
Property Id Maximum_child_age Maximum_infant_age
-------------------------------------------------
1 11 2
Desired output
RoomId Adult Child Infant PropertyId
--------------------------------
1 2 1 1 1
2 2 1 0 1
3 1 0 0 1
Use conditional aggregation :
SELECT
SUM(CASE WHEN pas.age > ppt.Maximum_child_age THEN 1 ELSE 0 END) AS Adult,
SUM(CASE WHEN pas.age BETWEEN Maximum_infant_age AND ppt.Maximum_child_age THEN 1 ELSE 0 END) AS Child,
SUM(CASE WHEN pas.age < ppt.Maximum_infant_age THEN 1 ELSE 0 END) AS Infant,
ppt.id
FROM
passengers pas
CROSS JOIN properties ppt
GROUP BY ppt.id
Cross join the properties and then do conditional aggregation.
SELECT count(CASE
WHEN pa.ages > pr.maximum_child_age THEN
1
END) adult,
count(CASE
WHEN pa.ages > pr.maximum_infant_age
AND pa.ages <= pr.maximum_child_age THEN
1
END) child,
count(CASE
WHEN pa.ages <= pr.maximum_infant_age THEN
1
END) infant,
pr.propertyid
FROM passengers pa
CROSS JOIN properties pr
GROUP BY pr.propertyid;

group by in case of nested cases with conditions on different tables

My question is a bit similar to this question but with a caveat. In my case the conditions are dependent on different tables, not one table. The part which is giving me trouble is the GROUP BY part. Here is the query:
SELECT
CASE
WHEN T1.ImportantColumn = 'Y'
THEN 'Good'
ELSE
CASE
WHEN T2.ImportantColumn = 1
THEN 'Very Good'
ELSE
CASE
WHEN T3.ImportantColumn IS NULL
THEN 'Bad'
ELSE T3.ImportantColumn
END
END
END AS WorkStatus,
SUM(case when T2.sex = 'M' THEN 1 ELSE 0 END) male ,
SUM(case when T2.sex = 'F' THEN 1 ELSE 0 END) female ,
COUNT(WorkStatus) AS [CountWorkStatus]
FROM
Condition1Table T1
RIGHT JOIN Condition2Table T2 ON T1.city = T2.Code_id AND T1.field_name = 'cities'
INNER JOIN Condition3Table T3 ON T2.student_id = T3.student_id
GROUP BY T3.ImportantColumn, T2.ImportantColumn, T1.ImportantColumn -- <-- wrote this but I know it's wrong
It is sort of IF ELSE scenario. If Condition1Table.ImportantColumn is 'Y' then 'Good', else if Condition2Table.ImportantColumn is 1 then 'Very Good', else if Condition3Table.ImportantColumn is NULL then 'bad', Else the value in Condition3Table.ImportantColumn. The hard part is the grouping of data in a desired format which is below:
WorkStatus | male | female | CountWorkStatus
---------- ----- ------ ---------------
Good | 3 | 7 | 10
Very Good | 11 | 2 | 13
Bad | 5 | 0 | 5
Val1 | 1 | 9 | 10
Val2 | 41 | 23 | 64
You seem to be asking "how do I group by a huge CASE statement without repeating the whole CASE statement"?
If so, just use a sub-query.
Then the result of the CASE statement has a column name that you can refer to.
There is near zero performance penalty here, sub-queries are expanded out macro-like. SQL is a declarative language, it's just a syntax for expressing a problem to be solved. When that's compiled down there's a program to run. So, while thinking about the SQL, you just need the syntax to express your problem.
SELECT
WorkStatus,
SUM(case when sex = 'M' THEN 1 ELSE 0 END) male ,
SUM(case when sex = 'F' THEN 1 ELSE 0 END) female ,
COUNT(WorkStatus) AS [CountWorkStatus]
FROM
(
SELECT
CASE
WHEN T1.ImportantColumn = 'Y'
THEN 'Good'
ELSE
CASE
WHEN T2.ImportantColumn = 1
THEN 'Very Good'
ELSE
CASE
WHEN T3.ImportantColumn IS NULL
THEN 'Bad'
ELSE T3.ImportantColumn
END
END
END AS WorkStatus,
T2.sex
FROM
Condition1Table T1
RIGHT JOIN Condition2Table T2 ON T1.city = T2.Code_id AND T1.field_name = 'cities'
INNER JOIN Condition3Table T3 ON T2.student_id = T3.student_id
)
AS StatusBySex
GROUP BY
WorkStatus

Grouping sub query in one row

ClientID Amount flag
MMC 600 1
MMC 700 1
FDN 800 1
FDN 350 2
FDN 700 1
Using sql server,Below query I am getting 2 rows fro FDN. I just would like to combine Client values in one row.
Output should be like
Client gtcount, totalAmountGreaterThan500 lscount,AmountLessThan500
MMC 2 1300 0 0
FDN 2 1500 1 350
SELECT
f.ClientID,f.flag,
case when flag = 1 then count(*) END as gtcount,
SUM(CASE WHEN flag = 1 THEN Amount END) AS totalAmountGreaterThan500,
case when flag = 2 then count(*) END as lscount,
SUM(CASE WHEN Flag = 2 THEN Amount END) AS AmountLessThan500,
from
( select ClientID, Amount,flag from #myTable)f
group by ClientID,f.flag
Try
SELECT ClientID,
SUM(CASE WHEN flag = 1 THEN 1 ELSE 0 END) AS gtcount,
SUM(CASE WHEN flag = 1 THEN Amount ELSE 0 END) AS totalAmountGreaterThan500,
SUM(CASE WHEN flag = 2 THEN 1 ELSE 0 END) AS lscount,
SUM(CASE WHEN Flag = 2 THEN Amount ELSE 0 END) AS AmountLessThan500
FROM Table1
GROUP BY ClientID
Output:
| CLIENTID | GTCOUNT | TOTALAMOUNTGREATERTHAN500 | LSCOUNT | AMOUNTLESSTHAN500 |
|----------|---------|---------------------------|---------|-------------------|
| FDN | 2 | 1500 | 1 | 350 |
| MMC | 2 | 1300 | 0 | 0 |
Here is SQLFiddle demo
Looks like your desired output is off -- there aren't any mmc records less than 500. You can accomplish this using sum with case for each of your fields, removing flag from the group by:
SELECT
ClientID,
SUM(CASE WHEN flag = 1 THEN 1 END) as gtcount,
SUM(CASE WHEN flag = 1 THEN Amount END) AS totalAmountGreaterThan500,
SUM(CASE WHEN flag = 2 THEN 1 END) as ltcount,
SUM(CASE WHEN Flag = 2 THEN Amount END) AS AmountLessThan500
from myTable
group by ClientID
SQL Fiddle Demo
On a different note, not sure why you need the Flag field. If it's just being used to denote less than records, just add the logic to the query:
SUM(CASE WHEN Amount <= 500 Then ...)