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

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

Related

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

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

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.

Entries in my SQL Query doubles the result values

I have 3 Tables (Transaction, TransactionEntry and TenderEntry). When I run my SQL query, I got the sum values twice because of TransactionEntry Table.
So let's say I have 2 Items (Clothe worth $288.75 and Free Umbrella worth $50.00). I settled the payment using Cash worth $300 and GC worth $100, now I have a Cash change worth $61.25.
Here are the entries in the 3 tables
Transaction table
TransactionNumber|Total|
========================
1 |338.75|
TransactionEntry Table
TransactionNumber|ItemID|Price|
===============================
1 |245648|288.75|
1 |129 |50.00 |
Tender Entry Table
TransactionNumber|TenderID|Description|Amount|
==============================================
1 |1 |Cash |300.00|
1 |1 |Cash |-61.25|
1 |20 |GC |100.00|
Here is what I made so far
select [Transaction].TransactionNumber,
(case when [Transaction].RecallType = 0 then [Transaction].total else 0 end) as Sales,
sum(case when TransactionEntry.ItemID = 6922 then TransactionEntry.Price
when TransactionEntry.price = 0 then TransactionEntry.price else 0 end) as Free,
sum(case when TenderEntry.tenderID = 1 then TenderEntry.Amount else 0 end) as Cash,
sum(case when TenderEntry.tenderID = 20 then TenderEntry.Amount else 0 end) as GC
from [Transaction] inner join
TransactionEntry on [Transaction].transactionnumber = TransactionEntry.transactionnumber inner join
TenderEntry on [Transaction].transactionnumber = TenderEntry.transactionnumber
group by [Transaction].TransactionNumber,
(case when [Transaction].RecallType = 0 then [Transaction].total else 0 end)
I get this kind of output
TransactionNumber|Sales |Free |Cash |GC |
==============================================
1 |338.75|150.00|477.50|200.00|
Instead of this
TransactionNumber|Sales |Free |Cash |GC |
==============================================
1 |338.75|50.00|238.75 |100.00|
You're wanting one row per transaction, so I'd suggest you sum your joined tables to the transaction level before doing the joins as follows:
select t1.TransactionNumber,
case when t1.RecallType = 0 then t1.total else 0 end as Sales,
t2.Free,
t3.Cash,
t3.GC
from [Transaction] t1
inner join (select transactionnumber,
sum(case when ItemID = 6922 then Price else 0 end) as free
from TransactionEntry
group by transactionnumber) as t2
on t1.transactionnumber = t2.transactionnumber
inner join (select transactionnumber,
sum(case when tenderID = 1 then Amount else 0 end) as Cash,
sum(case when tenderID = 20 then Amount else 0 end) as GC
from TenderEntry
group by transactionnumber) as t3
on t1.transactionnumber = t3.transactionnumber
Note that I've used table aliases (t1, t2, t3) to condense the code and make it a bit easier to read. It looks as though "Transaction" is a reserved word, so probably not a great choice of table name as it means you always have to use the square brackets to enclose the name when referring to it.
There seems to be an issue with this piece of your code,
sum(case when TransactionEntry.ItemID = 6922 then TransactionEntry.Price
when TransactionEntry.price = 0 then TransactionEntry.price else 0 end) as Free,
because your Umbrella, which you say is Free, is item 129 not 6922. Also the "when TransactionEntry.price = 0 then ..." is setting the price to 0 regardless of the price. I'm not sure what you intended there?

SQL To count values of multiple columns

I have a table, with columns like this:
name1,name2,name_thesame,adress1,adress2,adress_thesame,city1,city2,city_thesame
In all columns ending with _thesame, there is a true or false depending if name1 and name2 are the same, same with adress etc etc.
I now need a query that returns a count how many true and false i got for each of the _thesame columns.
Cant wrap my head around how to do this query - any body got some ideas or pointers? Thanks
For a single property you can do:
select name_thesame, count(*)
from table
group by name_thesame
This will give you results like:
true 10
false 15
If you want to have it as a list for multiple columns, you can just union the queries:
select 'Name', name_thesame, count(*)
from table
group by name_thesame
union
select 'Address', adress_thesame, count(*)
from table
group by adress_thesame
getting:
Name true 10
Name false 15
Address true 20
Address false 5
This is another option:
SELECT SUM(CASE WHEN name_thesame = true THEN 1 ELSE 0 END) as nametrue,
SUM(CASE WHEN name_thesame = false THEN 1 ELSE 0 END) as namefalse,
SUM(CASE WHEN adress_thesame = true THEN 1 ELSE 0 END) as adresstrue,
SUM(CASE WHEN adress_thesame = false THEN 1 ELSE 0 END) as adressfalse,
SUM(CASE WHEN city_thesame = true THEN 1 ELSE 0 END) as citytrue,
SUM(CASE WHEN city_thesame = false THEN 1 ELSE 0 END) as cityfalse
FROM yourTable
You can tweak it to deal with NULLs as well if relevant:
...
CASE WHEN name_thesame = false OR name_thesame IS NULL THEN 1 ELSE 0 END
...
or either NVL(), ISNULL(), IFNULL() or COALESCE(), depending on the DBMS you're using (syntax is always the same):
...
CASE WHEN COALESCE(name_thesame, false) = false THEN 1 ELSE 0 END
...
Result:
nametrue | namefalse | adresstrue | adressfalse | citytrue | cityfalse
5 | 7 | 2 | 1 | 10 | 8

SQL find total count of each type in a column

I'm learning SQL and am stumped on what should be a simple query. I have a table with the following pattern:
Id | Type
------------
1 | Red
2 | Blue
3 | Blue
4 | Red
..
I would like to write a query to return a table that counts the total number of instances of each type and returns a table with the following pattern, for example, if 'Blue' occurs in 12 rows, and 'Red' occurs in 16 rows in the table above, the result would be:
Blue | Red
-----------
12 | 16
You could do it this way:
SELECT Type, COUNT(*) FROM TABLE GROUP BY Type
If you'd like to see the Types in separate columns, you could do this:
SELECT SUM(CASE WHEN Type = 'Blue' THEN 1 ELSE 0 END) AS Blue, SUM(CASE WHEN Type = 'Red' THEN 1 ELSE 0 END) AS Red FROM TABLE
I suggest using count over partition by. Here's a code I wrote to help my company check for duplicate Technician EmployeeID's and Pincodes, including count and YES/NO columns to allow filtering in excel so they can see what corrections need to be made:
select
t.TechnicianId, t.TechnicianName, t.Pincode, t.EmployeeID
, [Pincode Count] = count(t.Pincode) over (partition by t.Pincode)
, [Duplicate Pincode?] = case count(t.Pincode) over (partition by t.Pincode) when 1 then 'NO' else 'YES' end
, [EmployeeID Count] = count(t.EmployeeID) over (partition by t.EmployeeID)
, [Duplicate EmployeeID?] = case count(t.EmployeeID) over (partition by t.EmployeeID) when 1 then 'NO' else 'YES' end
from Technicians t
group by t.TechnicianId, t.TechnicianName, t.Pincode, t.EmployeeID
order by 4