SQL Server Month Totals - sql

SQL Server newbie
The following query returns SRA by Student and month only if there is a record for a student in Discipline table. I need a query to return all students and month totals even if there is no record for student in Discipline table. Any direction appreciated
SELECT TOP 100 PERCENT MONTH(dbo.Discipline.DisciplineDate) AS [Month], dbo.Discipline.StuId, dbo.Stu.Lastname + ',' + dbo.Stu.FirstName AS Student,
SUM(CASE WHEN Discipline.SRA = 1 THEN 1 END) AS [Acad Suspension], SUM(CASE WHEN Discipline.SRA = 2 THEN 1 END) AS Conduct,
SUM(CASE WHEN Discipline.SRA = 3 THEN 1 END) AS Disrespect, SUM(CASE WHEN Discipline.SRA = 4 THEN 1 END) AS [S.R.A],
SUM(CASE WHEN Discipline.SRA = 5 THEN 1 END) AS Suspension, SUM(CASE WHEN Discipline.SRA = 6 THEN 1 END) AS Tone
FROM dbo.Discipline INNER JOIN
dbo.Stu ON dbo.Discipline.StuId = dbo.Stu.StuId
GROUP BY dbo.Discipline.StuId, dbo.Stu.Lastname, dbo.Stu.FirstName, MONTH(dbo.Discipline.DisciplineDate)
ORDER BY Student

You need to change the INNER JOIN onto dbo.Stu to a LEFT JOIN:
SELECT MONTH(d.disciplinedate) AS [Month],
d.StuId,
s.Lastname + ',' + s.FirstName AS Student,
SUM(CASE WHEN d.SRA = 1 THEN 1 END) AS [Acad Suspension],
SUM(CASE WHEN d.SRA = 2 THEN 1 END) AS Conduct,
SUM(CASE WHEN d.SRA = 3 THEN 1 END) AS Disrespect,
SUM(CASE WHEN d.SRA = 4 THEN 1 END) AS [S.R.A],
SUM(CASE WHEN d.SRA = 5 THEN 1 END) AS Suspension,
SUM(CASE WHEN d.SRA = 6 THEN 1 END) AS Tone
FROM dbo.Discipline d
LEFT JOIN dbo.Stu s ON s.stuid = d.stuid
GROUP BY d.StuId, s.Lastname, s.FirstName, MONTH(d.DisciplineDate)
ORDER BY Student
The LEFT JOIN means that whatever table you're LEFT JOINing to might not have records to support the JOIN, but you'll still get records from the base table (dbo.Discipline).
I used table aliases - d and s. Less to type when you need to specify references.

generate a series of months, join discipline to that.

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

How could I adapt this query to work over multiple years?

This query pulls data from a VistaDB and produces info on the number of courses started in each month of the year from people in different countries.
Select c.CountryName As Country,
Count (case When Month( ch.CourseStarted ) = 1 Then 1 End) As Jan19,
Count (case when Month(ch.CourseStarted ) = 2 Then 1 End) as Feb19,
Count (case When Month(ch.CourseStarted ) = 3 Then 1 End) as Mar19,
Count (case When Month(ch.CourseStarted ) = 4 Then 1 End) as Apr19,
Count (case When Month(ch.CourseStarted ) = 5 Then 1 End) as May19,
Count (case When Month(ch.CourseStarted ) = 6 Then 1 End) as Jun19,
Count (case When Month(ch.CourseStarted ) = 7 Then 1 End) as Jul19,
Count (case When Month(ch.CourseStarted ) = 8 Then 1 End) as Aug19,
Count (case When Month(ch.CourseStarted ) = 9 Then 1 End) as Sep19,
Count (case When Month(ch.CourseStarted ) = 10 Then 1 End) as Oct19,
Count (case When Month(ch.CourseStarted ) = 11 Then 1 End)as Nov19,
Count (case When Month(ch.CourseStarted ) = 12 Then 1 End) as Dec19
From Country As c
Inner Join CourseHistory As ch On c.Oid = ch.Country
Where (ch.CourseStarted >= '2019-01-01' And
ch.CourseStarted <= '2019-12-31')
Group By c.CountryName
Order by c.CountryName;
My question is would it be possible to make this semi-dynamic so that if I were to make the final date in the where clause '2022-12-31' I could get a rafft of colums for each month of each year?

invalid identifier : sum of multiple column in sql

I'm trying to calculate multible columns in this query
SELECT
SUM (CASE WHEN B.ID = 1 THEN 1 END) AS OPD,
SUM (CASE WHEN B.ID = 2 THEN 1 END) AS IPD,
SUM (CASE WHEN B.ID = 3 THEN 1 END) AS DC,
SUM (CASE WHEN B.ID = 4 THEN 1 END) AS PROC,
SUM (CASE WHEN B.ID = 5 THEN 1 END) AS SUR,
(OPD + IPD + PROC) as Total
FROM REF_TB_APP_TRANSACTIONS A,
REF_VW_VISIT_TYPE B
WHERE A.REQ_VISIT_TYPE = B.ID
AND A.TO_EST_CODE = 20068;
but I got this error PROC invalid identifier
You can't add the three SUMS in the Total column in the SELECT directly, since you're using the aliases of those columns. You could just do your Total column with another SUM CASE.
SELECT
SUM (CASE WHEN B.ID = 1 THEN 1 END) AS OPD,
SUM (CASE WHEN B.ID = 2 THEN 1 END) AS IPD,
SUM (CASE WHEN B.ID = 3 THEN 1 END) AS DC,
SUM (CASE WHEN B.ID = 4 THEN 1 END) AS [PROC],
SUM (CASE WHEN B.ID = 5 THEN 1 END) AS SUR,
SUM (CASE WHEN B.ID IN (1,2,4)THEN 1 END) AS Total
FROM REF_TB_APP_TRANSACTIONS A,
REF_VW_VISIT_TYPE B
WHERE A.REQ_VISIT_TYPE = B.ID
AND A.TO_EST_CODE = 20068;
Depending on the DBMS you are using. You cant sum columns that are aliased like that, you would have to use a sub select and do the sum from there. If you verify your DBMS we can create query.
If MS SQL the below will work. A couple things:
PROC is reserved word, so either change that or put brackets around it (I went for brackets). Also it is preferred if you use JOINS vs. the way you had the queries.
SELECT OPD, IPD, DC, [PROC], SUR, (OPD + IPD + [PROC]) as Total
FROM (
SELECT
SUM (CASE WHEN B.ID = 1 THEN 1 END) AS OPD,
SUM (CASE WHEN B.ID = 2 THEN 1 END) AS IPD,
SUM (CASE WHEN B.ID = 3 THEN 1 END) AS DC,
SUM (CASE WHEN B.ID = 4 THEN 1 END) AS [PROC],
SUM (CASE WHEN B.ID = 5 THEN 1 END) AS SUR
FROM REF_TB_APP_TRANSACTIONS A
INNER JOIN REF_VW_VISIT_TYPE B ON A.REQ_VISIT_TYPE = B.ID
WHERE A.TO_EST_CODE = 20068
) SUB
You can't reference the aliased columns as part of the select because in the order of query execution, they don't exist yet.
You simply wrap your query so it becomes a derived table and then you can refer to them in an outer select, see:
select OPD, IPD, DC, [PROC], SUR, OPD + IPD + [PROC] as Total from (
SELECT
SUM (CASE WHEN B.ID = 1 THEN 1 END) AS OPD,
SUM (CASE WHEN B.ID = 2 THEN 1 END) AS IPD,
SUM (CASE WHEN B.ID = 3 THEN 1 END) AS DC,
SUM (CASE WHEN B.ID = 4 THEN 1 END) AS [PROC],
SUM (CASE WHEN B.ID = 5 THEN 1 END) AS SUR
FROM REF_TB_APP_TRANSACTIONS A
join REF_VW_VISIT_TYPE B on B.ID=A.REQ_VISIT_TYPE
where A.TO_EST_CODE = 20068
)x
Guessing because you have a semi-colon this is SQLServer, in which case you will need to use [] around the reserved word PROC
I've also properly joined your tables as it's not 1989 any more :-0

How to use a conditional aggregate for total gross

I have this query:
SELECT CAST(co.DateCreated AS DATE) AS Date,
SUM(w.Gross),
SUM(CASE WHEN co.BookingSourceId = 1 THEN 1 ELSE 0 END) as Website,
SUM(CASE WHEN co.BookingSourceId = 2 THEN 1 ELSE 0 END) as Phone,
COUNT(*) as Total_Orders
FROM [Sterlingbuild].[dbo].[CustomerOrder] co
INNER JOIN ( SELECT Gross, CustomerOrderId, p.ProductBrandId
FROM [Sterlingbuild].[dbo].[CustomerOrderItem] coi
INNER JOIN [Sterlingbuild].[dbo].[Product] p ON coi.ProductId = p.ProductId
) w ON co.CustomerOrderId = w.CustomerOrderId
WHERE CustomerOrderStatusId = 7 AND DepartmentId = 1 AND w.ProductBrandId = 7
GROUP BY CAST(co.DateCreated AS DATE)
ORDER BY CAST(co.DateCreated AS DATE)
At the moment it returns the number of orders made by phone/website however I want the total GROSS for both phone/website how do I amend the query to achieve this.
Using SQL server
Sum the gross:
SUM(CASE WHEN co.BookingSourceId = 1 THEN w.gross ELSE 0 END) as Website_gross,
SUM(CASE WHEN co.BookingSourceId = 2 THEN w.gross ELSE 0 END) as Phone_gross,

Combining unrelated queries into one query to produce counts

I would like a stored procedure to run daily that produces a report of counts.
For example, the .csv would look something like this:
Daily,1
Deaths,0
In-House EKG,4
In-House Xray,2
Suicidal Patients,12
HIV,0
Their individual queries look something like this:
-- Daily and Death Counts
select
SUM(CASE WHEN location != '[OUT]' THEN 1 ELSE 0 END) as 'Daily',
SUM(CASE WHEN death = 1 THEN 1 ELSE 0 END) as 'Deaths'
from
patient_data
-- In-House Tasks
select
SUM(CASE WHEN cat_id = 72 THEN 1 ELSE 0 END) as 'In-House EKG',
SUM(CASE WHEN cat_id = 73 THEN 1 ELSE 0 END) as 'In-House XRay',
from
organizer_tasks
-- Suicidal Patients
select
count(distinct(pid)) as 'Suicidal Inmates'
from
problems pr
inner join problem_list pl on pl.id = pr.problem_list_id
where
pr.status = 'open'
and pl.title like '%suicide%'
-- HIV
select
count(distinct(pid)) as 'HIV'
from
problems pr
inner join problem_list pl on pl.id = pr.problem_list_id
inner join patient_data pd on pr.pid = pd.pid
where
pr.status = 'open'
and pl.title like '%hiv%'
As you can see, each set of data comes from a different table, and has no relation. How can I accomplish my desired result set?
Thanks.
-- Daily and Death Counts
select * from (
select
SUM(CASE WHEN location != '[OUT]' THEN 1 ELSE 0 END) as 'Daily',
SUM(CASE WHEN death = 1 THEN 1 ELSE 0 END) as 'Deaths'
from
patient_data
) tmp unpivot (Number for Type in ([Daily], [Deaths])) t
union all
-- In-House Tasks
select * from (
select
SUM(CASE WHEN cat_id = 72 THEN 1 ELSE 0 END) as 'In-House EKG',
SUM(CASE WHEN cat_id = 73 THEN 1 ELSE 0 END) as 'In-House XRay'
from
organizer_tasks
) tmp unpivot (Number for Type in ([In-House EKG], [In-House XRay])) t
union all
-- Suicidal Patients
select 'Suicidal Inmates',
count(distinct(pid))
from
problems pr
inner join problem_list pl on pl.id = pr.problem_list_id
where
pr.status = 'open'
and pl.title like '%suicide%'
union all
-- HIV
select 'HIV',
count(distinct(pid))
from
problems pr
inner join problem_list pl on pl.id = pr.problem_list_id
inner join patient_data pd on pr.pid = pd.pid
where
pr.status = 'open'
and pl.title like '%hiv%'
Try this with Union which is form in one Query :
select
SUM(CASE WHEN location != '[OUT]' THEN 1 ELSE 0 END) as 'Daily'
from
patient_data
UNION ALL
select
SUM(CASE WHEN death = 1 THEN 1 ELSE 0 END) as 'Deaths'
from
patient_data
UNION ALL
-- In-House Tasks
select
SUM(CASE WHEN cat_id = 72 THEN 1 ELSE 0 END) as 'In-House EKG'
from
organizer_tasks
UNION ALL
select
SUM(CASE WHEN cat_id = 73 THEN 1 ELSE 0 END) as 'In-House XRay'
from
organizer_tasks
UNION ALL
-- Suicidal Patients
select
count(distinct(pid)) as 'Suicidal Inmates'
from
problems pr
inner join problem_list pl on pl.id = pr.problem_list_id
where
pr.status = 'open'
and pl.title like '%suicide%'