Trouble ordering GROUP BY, ORDER BY AND JOIN - sql

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;

Related

INNER JOIN with aggregate functions in my SELECT

I'm trying to join a new column to my current query that uses aggregate functions. I create this column with a new query that also uses an aggregate function from a different table but I'm not sure if a JOIN will work for me since I need to join it to its respective row.
TABLE A (employees that are enrolled or were enrolled in a project)
ID
DEPARTMENT
ENROLLED
PROJECT
1
MARKETING
Yes
ARQ
2
MARKETING
Yes
TC
3
MARKETING
No
ARQ
4
MARKETING
No
TC
5
FINANCE
Yes
ARQ
6
FINANCE
Yes
TC
7
FINANCE
No
ARQ
8
FINANCE
Yes
TC
This table has more departments and more projects, but I simplified.
TABLE B (relation with departments and employees)
ID
DEPARTMENT
TOTAL_EMPLOYEES
1
MARKETING
2
2
MARKETING
3
3
FINANCE
4
4
FINANCE
8
In my first query I was asked to achieve the following result - using only table A:
(employees enrolled) (employees not enrolled)
DEPARTMENT
ARQ_E
TC_E
TOTAL_ENROLLED
ARQ_N
TC_N
TOTAL_NOT_ENROLLED
TOTAL
MARKETING
1
1
2
1
1
2
4
FINANCE
1
1
2
1
1
2
4
Using the following query:
SELECT tableA.department,
sum(case when enrolled = 'Yes' and tableA.project = 'ARQ' then 1 else 0 end) as ARQ_E,
sum(case when enrolled = 'Yes' and tableA.project = 'TC' then 1 else 0 end) as TC_E,
sum(case when enrolled = 'Yes' then 1 else 0 end) as TOTAL_ENROLLED,
sum(case when enrolled != 'Yes' and tableA.project = 'ARQ' then 1 else 0 end) as ARQ_N,
sum(case when enrolled != 'Yes' and tableA.project = 'TC' then 1 else 0 end) as TC_N,
sum(case when enrolled != 'Yes' then 1 else 0 end) as TOTAL_NOT_ENROLLED,
count (*) AS Total
FROM tableA
GROUP BY tableA.department;
My second query gets departments and their total employees from table B:
DEPARTMENT
TOTAL_EMPLOYEES
MARKETING
5
FINANCE
12
Using the following query:
SELECT tableB.department,
sum(tableB.total_employees) AS TOTAL_EMPLOYEES
FROM tableB
GROUP BY tableB.department;
I need to add the column TOTAL_EMPLOYEES to my first query, next to TOTAL will be TOTAL_EMPLOYEES. But it has to be placed with its respective department row. I need this to compare this 2 columns and see how many employees were not assigned to any project.
This is my expected result.
(employees enrolled) (employees not enrolled)
DEPARTMENT
ARQ_E
TC_E
TOTAL_ENROLLED
ARQ_N
TC_N
TOTAL_NOT_ENROLLED
TOTAL
T_EMPL
MARKETING
1
1
2
1
1
2
4
5
FINANCE
1
1
2
1
1
2
4
12
I have tried to achieve this using the following query:
SELECT tableA.department,
sum(case when enrolled = 'Yes' and tableA.project = 'ARQ' then 1 else 0 end) as ARQ_E,
sum(case when enrolled = 'Yes' and tableA.project = 'TC' then 1 else 0 end) as TC_E,
sum(case when enrolled = 'Yes' then 1 else 0 end) as TOTAL_ENROLLED,
sum(case when enrolled != 'Yes' and tableA.project = 'ARQ' then 1 else 0 end) as ARQ_N,
sum(case when enrolled != 'Yes' and tableA.project = 'TC' then 1 else 0 end) as TC_N,
sum(case when enrolled != 'Yes' then 1 else 0 end) as TOTAL_NOT_ENROLLED,
count (*) AS Total,
sum (tableB.total_employees) AS T_EMPL
FROM tableA
JOIN tableB
ON tableA.department = tableB.department
GROUP BY tableA.department;
But the numbers I get in my query are completely wrong since the JOINS repeat my rows and my SUMS duplicate.
I don't know if I really need to use a join or a subquery to place my sum(tableB.department) in its respective row.
I'm using PostgreSQL but since I'm using Standard 92 any SQL solution will help.
Your main issue stemmed from inadvertently multiplying rows with the join,
and has already been addressed. See:
Two SQL LEFT JOINS produce incorrect result
But use the standard SQL aggregate FILTER clause. It's shorter, cleaner, and noticeably faster. See:
Aggregate columns with additional (distinct) filters
For absolute performance, is SUM faster or COUNT?
SELECT *
FROM (
SELECT department
, count(*) FILTER (WHERE enrolled AND project = 'ARQ') AS arq_e
, count(*) FILTER (WHERE enrolled AND project = 'TC') AS tc_e
, count(*) FILTER (WHERE enrolled) AS total_enrolled
, count(*) FILTER (WHERE NOT enrolled AND project = 'ARQ') AS arq_n
, count(*) FILTER (WHERE NOT enrolled AND project = 'TC') AS tc_n
, count(*) FILTER (WHERE NOT enrolled) AS total_not_enrolled
, count(*) AS total
FROM tableA a
GROUP BY 1
) a
LEFT JOIN ( -- !
SELECT department
, sum(total_employees) AS total_employees
FROM tableB b
GROUP BY 1
) b USING (department);
enrolled should be a boolean column. Make it so if it isn't. Then you can use it directly. Smaller, faster, cleaner, shorter code.
I replaced the [INNER] JOIN with a LEFT [OUTER] JOIN on a suspicion. Typically, you want to keep all results, even if the same department is not found in the other table. Maybe even a FULL [OUTER] JOIN?
Also, USING (department) as join condition conveniently outputs that column only once, so we can make do with SELECT * in the outer SELECT.
Finally, subqueries are shorter and faster than CTEs. Not much since Postgres 12, but still. See:
Does WITH query store the results of referred tables?
Join the results of the two queries, using sub-queries, don't join the tables.
That way you're joining 1 row of enrollment data per department to 1 row of employee data per department.
SELECT
*
FROM
(
SELECT tableA.department,
sum(case when enrolled = 'Yes' and tableA.project = 'ARQ' then 1 else 0 end) as ARQ_E,
sum(case when enrolled = 'Yes' and tableA.project = 'TC' then 1 else 0 end) as TC_E,
sum(case when enrolled = 'Yes' then 1 else 0 end) as TOTAL_ENROLLED,
sum(case when enrolled != 'Yes' and tableA.project = 'ARQ' then 1 else 0 end) as ARQ_N,
sum(case when enrolled != 'Yes' and tableA.project = 'TC' then 1 else 0 end) as TC_N,
sum(case when enrolled != 'Yes' then 1 else 0 end) as TOTAL_NOT_ENROLLED,
count (*) AS Total
FROM tableA
GROUP BY tableA.department
)
AS enroll
INNER JOIN
(
SELECT tableB.department,
sum(tableB.total_employees) AS Total_EMPLOYEES
FROM tableB
GROUP BY tableB.department
)
AS employee
ON employee.department = enroll.department
As the join will multiply the summ, you can first sum the values and then join them
WITH CTE1 as (SELECT tableA.department,
sum(case when enrolled = 'Yes' and tableA.project = 'ARQ' then 1 else 0 end) as ARQ_E,
sum(case when enrolled = 'Yes' and tableA.project = 'TC' then 1 else 0 end) as TC_E,
sum(case when enrolled = 'Yes' then 1 else 0 end) as TOTAL_ENROLLED,
sum(case when enrolled != 'Yes' and tableA.project = 'ARQ' then 1 else 0 end) as ARQ_N,
sum(case when enrolled != 'Yes' and tableA.project = 'TC' then 1 else 0 end) as TC_N,
sum(case when enrolled != 'Yes' then 1 else 0 end) as TOTAL_NOT_ENROLLED,
count (*) AS Total
FROM tableA
GROUP BY tableA.department),
CTE2 as (SELECT tableB.department,
sum(tableB.total_employees) AS TOTAL_EMPLOYEES
FROM tableB
GROUP BY tableB.department)
SELECT
CTE1.department, ARQ_E, TC_E, TOTAL_ENROLLED, ARQ_N, TC_N, TOTAL_NOT_ENROLLED, TOTAL, T_EMPL,CTE2.TOTAL_EMPLOYEES
FROM CTE1 JOIN CTE2 ON CTE1.department = CTE2.department

SQL Group By with multiple counts

I'm trying to group a list of services together along with the number of applicants in each service, but I also need a count on the status each applicant is in.
Applicants table
serviceID clientID applicantID status
----------------------------------------------------
1 41 1 1 (Processing)
1 41 16 1 (Processing)
1 41 15 2 (Ready)
2 41 12 1 (Processing)
2 41 18 3 (Complete)
Service table:
serviceID serviceName
--------------------------
1 Full Service
2 Part Service
Results need to look like:
serviceName totalApplicants processingCount readyCount completeCount
---------------------------------------------------------------------------
Full Service 3 2 1 0
Part Service 2 1 0 1
I've got the following, but it's returning the same count in each of the columns:-
SELECT
Services.serviceName,
(COUNT(Applicants.applicantID)) AS totalApplicants,
ISNULL(SUM(CASE WHEN Applicants.status = 1 THEN 1 ELSE 0 END), 0) AS processingCount,
ISNULL(SUM(CASE WHEN Applicants.status = 2 THEN 1 ELSE 0 END), 0) AS readyCount,
ISNULL(SUM(CASE WHEN Applicants.status = 3 THEN 1 ELSE 0 END), 0) AS completeCount
FROM
Applicants
LEFT JOIN
Services ON Applicants.serviceID = Services.serviceID
WHERE
Applicants.clientID = #CompanyID
GROUP BY
Services.serviceName
You can do conditional aggregation:
select
s.serviceName,
count(s.serviceID) totalApplicants,
sum(case when status = 1 then 1 else 0 end) processingCount,
sum(case when status = 2 then 1 else 0 end) readyCount,
sum(case when status = 3 then 1 else 0 end) completeCount
from service s
left join applicants a on a.serviceID = s.serviceID AND a.clientID = #CompanyID
group by s.serviceID, s.serviceName
The conditional expression use standard case expression; depending on the database that you are actually using, neater alternatives may exists.
Your query should be fine, but it can be simplified to:
SELECT s.serviceName,
COUNT(a.AppicantId) AS totalApplicants,
SUM(CASE WHEN a.status = 1 THEN 1 ELSE 0 END) AS processingCount,
SUM(CASE WHEN a.status = 2 THEN 1 ELSE 0 END) AS readyCount,
SUM(CASE WHEN a.status = 3 THEN 1 ELSE 0 END) AS completeCount
FROM Services s LEFT JOIN
Applicants a
ON a.serviceID = s.serviceID AND
a.clientID = #CompanyID
GROUP BY s.serviceName ;
Notes:
It looks like you want a row for every service, so that should be the first table in the LEFT JOIN.
Hence the filtering on the company goes into the ON clause.
Table aliases make the query easier to write and to read.
No ISNULL() is needed (and I prefer COALESCE() over ISNULL()).

multiple count conditions with single query

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

How to count for each ID, how many of each Type of outcomes(1,2,3) has that ID diagnosised?

I have tried many times but i have no idea how to group the answer for each technician for each diagnosis outcome (Only three types 1,2,3) that they have? (For example, how many diagnosis outcome 1 did technicianID 54 have .)
I try to put in the count function but it is not works.Could someone give me some idea in SQL?
It is what i found in the database.
TechnicianID FirstName
------------ -------------
54 Wayne
TechnicianID DiagnosisOutcomeID
------------ ------------------
54 1
54 2
54 1
54 3
The Result should be
TechnicianID FirstName DiagnosisOutcome=1 DiagnosisOutcome=2 DiagnosisOutcome=3
------------ -------------
54 Wayne
ThankYou
Since you have not mentioned any RDBMS, the query below will work on many RDBMS,
SELECT a.TechnicianID,
a.FirstName,
SUM(CASE WHEN b.DiagnosisOutcomeID = 1 THEN 1 ELSE 0 END) DiagnosisOutcomeID1,
SUM(CASE WHEN b.DiagnosisOutcomeID = 2 THEN 1 ELSE 0 END) DiagnosisOutcomeID2,
SUM(CASE WHEN b.DiagnosisOutcomeID = 3 THEN 1 ELSE 0 END) DiagnosisOutcomeID3
FROM Table1 a
INNER JOIN Table2 b
ON a.TechnicianID = b.TechnicianID
GROUP BY a.TechnicianID, a.FirstName
SQLFiddle Demo
If you are using RDBMS that supports PIVOT function,
SELECT TechnicianID,
FirstName,
DiagnosisOutcomeID1,
DiagnosisOutcomeID2,
DiagnosisOutcomeID3
FROM
(
SELECT a.TechnicianID,
a.FirstName,
'DiagnosisOutcomeID' + CAST(b.DiagnosisOutcomeID AS VARCHAR(5)) AS DiagnosisOutcomeID
FROM Table1 a
INNER JOIN Table2 b
ON a.TechnicianID = b.TechnicianID
) data
PIVOT
(
COUNT(DiagnosisOutcomeID)
FOR DiagnosisOutcomeID IN (DiagnosisOutcomeID1,DiagnosisOutcomeID2,DiagnosisOutcomeID3)
) pvt
ORDER BY TechnicianID
SQLFiddle Demo
The CASE statement is what you need.
Have a look at this SQL Fiddle
SELECT T.FirstName
,SUM(CASE WHEN DiagnosisOutcomeID = 1 THEN 1 ELSE 0 END) AS OUTCOME1
,SUM(CASE WHEN DiagnosisOutcomeID = 2 THEN 1 ELSE 0 END) AS OUTCOME2
,SUM(CASE WHEN DiagnosisOutcomeID = 3 THEN 1 ELSE 0 END) AS OUTCOME3
FROM Tech T
INNER JOIN
Diagnosis D
ON T.TechnicianID = D.TechnicianID
GROUP BY
T.FirstName

How do I return count and not count in a SQL query?

If I have a table
AgentID | IsNew | TeamID
1 N 1
2 Y 2
3 Y 2
4 N 2
5 Y 1
I want to return the following from a query:
Team | CountIsNew = N | CountIsNew = Y
1 1 1
2 1 2
Is there a way I can do this?
Using Oracle 10
SELECT team, SUM(DECODE(IsNew, 'N', 1, 0)), SUM(DECODE(IsNew, 'Y', 1, 0))
FROM mytable
GROUP BY
team
SELECT TeamId
, SUM(CASE WHEN IsNew = 'N' THEN 1 ELSE 0 END) AS CountIsNotNew
, SUM(CASE WHEN IsNew = 'Y' THEN 1 ELSE 0 END) AS CountIsNew
FROM Agent
GROUP BY TeamId
Yet another way - COUNT doesn't count NULLs (except for COUNT(*)):
SELECT TeamId,
COUNT(DECODE(IsNew,'N',1)) CountIsNotNew,
COUNT(DECODE(IsNew,'Y',1)) CountIsNew
FROM Agent
GROUP BY TeamId;
Or, if you prefer CASE:
SELECT TeamId,
COUNT(CASE IsNew WHEN 'N' THEN 1 END) CountIsNotNew,
COUNT(CASE IsNew WHEN 'Y' THEN 1 END) CountIsNew
FROM Agent
GROUP BY TeamId;
(note: the "1"s could be any literal value)