SQL count categorized by different amounts - sql

As the title suggests. I'm trying to write a query that will give me a count of all people who haven't attended something, however I then need to group them by how many times they haven't attended. Sort of like this.
|---------------------|-----------------------------------|
| No. Of People | No. of times not attended |
|---------------------|-----------------------------------|
| 12 | 1 |
|---------------------|-----------------------------------|
| 34 | 2 |
|---------------------|-----------------------------------|
In this sort of format, with the 1 meaning 'didnt attend once' and the 2 meaning 'didn't attend twice' etc etc.
This is what I have for now..
SELECT COUNT(p.PersonID)AS 'No. of People'
,COUNT(e.attended) AS 'Attended'
,et.EpisodeTypeName
FROM Person p
JOIN Episode e ON e.PersonID = p.PersonID
JOIN EpisodeType et ON et.EpisodeTypeID = e.EpisodeTypeID
WHERE e.Attended = 'No'
AND e.EpisodeDate >= '2015-04-01' AND e.EpisodeDate <= '2016-03-31'
GROUP BY e.Attended, et.EpisodeTypeName;
Any help with this would be great!

If I understand correctly, this is a histogram-of-histograms query. Also, I don't see that EpisodeType is needed, at least based on the results and query in the question.
So, a query with two levels of aggregation:
SELECT NotAttended, COUNT(*) as NumPeople
FROM (SELECT p.PersonID, COUNT(*) as NotAttended
FROM Person p JOIN
Episode e
ON e.PersonID = p.PersonID
WHERE e.Attended = 'No' AND
e.EpisodeDate >= '2015-04-01' AND e.EpisodeDate <= '2016-03-31'
GROUP BY p.PersonID
) p
GROUP BY NotAttended;

Related

SQL MAX aggregate function not bringing the latest date

Purpose: I am trying to find the max date of when the teachers made a purchase and type.
Orders table
ID
Ordertype
Status
TeacherID
PurchaseDate
SchoolID
TeacherassistantID
1
Pencils
Completed
1
1/1/2021
1
1
2
Paper
Completed
1
3/5/2021
1
1
3
Notebooks
Completed
1
4/1/2021
1
1
4
Erasers
Completed
2
2/1/2021
2
2
Teachers table
TeacherID
Teachername
1
Mary Smith
2
Jason Crane
School table
ID
schoolname
1
ABC school
2
PS1
3
PS2
Here is my attempted code:
SELECT o.ordertype, o.status, t.Teachername, s.schoolname
,MAX(o.Purchasedate) OVER (PARTITION by t.ID) last_purchase
FROM orders o
INNER JOIN teachers t ON t.ID=o.TeacherID
INNER JOIN schools s ON s.ID=o.schoolID
WHERE o.status in ('Completed','In-progress')
AND o.ordertype not like 'notebook'
It should look like this:
Ordertype
Status
teachername
last_purchase
schoolname
Paper
Completed
Mary Smith
3/5/2021
ABC School
Erasers
Completed
PS1
2/1/2021
ABC school
It is bringing multiple rows instead of just the latest purchase date and its associated rows. I think i need a subquery.
Aggregation functions are not appropriate for what you are trying to do. Their purpose is to summarize values in multiple rows, not to choose a particular row.
Just a window function does not filter any rows.
You want to use window functions with filtering:
SELECT ordertype, status, Teachername, schoolname, Purchasedate
FROM (SELECT o.ordertype, o.status, t.Teachername, s.schoolname,
o.Purchasedate,
ROW_NUMBER() OVER (PARTITION by t.ID ORDER BY o.PurchaseDate DESC) as seqnum
FROM orders o JOIN
teachers t
ON t.ID = o.TeacherID
schools s
ON s.ID = o.schoolID
WHERE o.status in ('Completed', 'In-progress') AND
o.ordertype not like 'notebook'
) o
WHERE seqnum = 1;
You can use it in different way. it's better to use Group By for grouping the other columns and after that use Order by for reorder all records just like bellow.
SELECT top 1 o.ordertype, o.status, t.Teachername, s.schoolname
,o.Purchasedate
FROM orders o
INNER JOIN teachers t ON t.ID=o.TeacherID
INNER JOIN schools s ON s.ID=o.schoolID
having o.status in ('Completed','In-progress')
AND o.ordertype not like 'notebook'
group by o.ordertype, o.status, t.Teachername, s.schoolname
order by o.Purchasedate Desc

How to retrieve most recent past date from current date and most future date from current date in sql

I am working on a small application that records appointment visits of patients. I have a both a Patient table and Appointment table.
In Appointment table, I have the following fields: AppointmentId, AppointmentDate, PatientId
In Patient table, I have PatientId, PatientName
In the Appointment table I have records as follows:
1 | 2020-08-08 | 2
2 | 2020-10-11 | 2
3 | 2020-12-15 | 2
4 | 2020-12-24 | 2
What I want to retrieve is that for all patients, I fetch the datasets of the PatientId. I want to retrieve the last appointment date, i.e 2020-10-11 and next appointment date, i.e 2020-12-15 if I run the query today.
I have tried the following but no getting the desired results. Can anyone please help me with this SQL query?
SELECT
patient.PatientName,
MAX(appointment.AppointmentDate) AS NextVisit,
MIN(appointment.AppointmentDate) AS LastVisit
FROM
Patient patient
INNER JOIN
Appointment appointment ON patient.PatientId = appointment.PatientId
You can use left joins in sql as follows:
Select p.patientid, patientname,
Max(ap.appointmentdate) as previous_appointment,
Min(an.appointmentdate) as next _appointment
from patient p
Left join appointment ap on p.patientid = ap.patientid and ap.appointmentdate < sysdate
Left join appointment an on p.patientid = an.patientid and an.appointmentdate > sysdate
Group by p.patientid, p.patientname
You can also use the conditional aggregation as follows:
Select p.patientid, patientname,
Max(case when ap.appointmentdate < sysdate then ap.appointmentdate end) as previous_appointment,
Min(case when ap.appointmentdate > sysdate then ap.appointmentdate end) as next _appointment
from patient p
Left join appointment ap on p.patientid = ap.patientid
Group by p.patientid, p.patientname
Please use >= or <= instead of < and > in condition according to your requirement.

Left join not producing correct result

I want to return all people regardless of whether they have a sale on any given day. My initial response to solve this would be
SELECT
P.[Name], P.[id],
F.[DATE], F.[Figure], F.[id]
FROM
Person P
LEFT JOIN
Figure F ON P.id = F.id
An example of the result for this is
Name id DATE Figure
--------------------------------------------
Tom 1 2017-09-27 15
Fred 2 2017-09-27 20
Jane 3 2017-09-25 0
Ben 4 2017-09-25 0
Now as soon as I limit the date to today's date, I get nothing.
SELECT
P.[Name], P.[id],
F.[DATE], F.[Figure], F.[id]
FROM
Person P
LEFT JOIN
Figure F ON P.id = F.id
WHERE
F.Date = GETDATE()
I understand this is probably because there is no one with today's date attached to them but how do I still display all of the people even if they had nothing for today? I thought that a left join would accomplish this but it doesn't.
My desired results would be :
Name id DATE Figure
--------------------------------------------
Tom 1 2017-10-02 0
Fred 2 2017-10-02 0
Jane 3 2017-10-02 0
Ben 4 2017-10-02 0
There are no keys in the tables so I feel as though that might be why the left join isn't working as I expected but has anyone got any ideas how I can get the desired result?
First, the condition needs to go in the ON clause rather than the WHERE clause, because it is on the second table.
Second, getdate() -- despite its name -- has a time component. So, convert it to a date:
FROM Person P LEFT JOIN
Figure F
ON P.id = F.id AND F.Date = CAST(getdate() as DATE)
Put your query in a cte and then pull from it to achieve your desired results:
;with cte_example
as
(SELECT
P.[Name],
P.[id],
F.[DATE], F.[Figure], F.[id]
FROM
Person P
LEFT JOIN Figure F
ON P.id = F.id)
select Name
,id
,cast(getdate() as date) as date
,case when date = convert(date, getdate()) then Figure else 0 end as Figure
from cte_example
I made a rextester example so you could see

SQL Server joining two tables, order by and display one record

I am having trouble with a SQL Server statement. The perfect scenario is the order and another table (jobs) by date created then display the contact information in descending order. Currently I can get the script to show all records, however if the user has more than one job then they are displayed more than once.
SELECT
c.*,
p.date_created
FROM
[db].[dbo].[Contact] AS c
JOIN
[db].[dbo].[job] AS p ON p.contact_id = c.contact_id
UNION
SELECT
*,
0 as date_created
FROM
[db].[dbo].[Contact]
ORDER BY
p.date_created DESC
The output
contact_id| date_created | contact_name
1 | 8/29/2016 1:07:18 PM | sam
1 | 8/26/2016 1:04:01 PM | sam
14 | 8/24/2016 5:07:22 PM | steve
The final output should just show the newest date created and for one user. Help is much appreciated.
The column in union select must match for number and type so convert 0 in a proper date
SELECT
c.contact_id
,max(p.date_created)
,c. contact_name
FROM [db].[dbo].[Contact] AS c
JOIN [db].[dbo].[job] AS p
ON p.contact_id = c.contact_id
GROUP BY c.contact_id,c. contact_name
union
select
c.contact_id
, convert(datetime, '01/01/1070', 101) as date_created
, c. contact_name
from [db].[dbo].[Contact]
ORDER BY p.date_created desc`
The result you need anyway should be obtainable with only
SELECT
c.contact_id
,max(p.date_created) as max_date_created
,c. contact_name
FROM [db].[dbo].[Contact] AS c
LEFT JOIN [db].[dbo].[job] AS p
ON p.contact_id = c.contact_id
GROUP BY c.contact_id,c. contact_name
ORDER BY c.contact_id,c. contact_name, max_date_created

Select records that appear more than once

I am trying to select records that appear more than once and are part of a specific department plus other departments.
So far the query that I have is this:
SELECT employeeCode, employeeName
FROM
Employees
WHERE
Department <> 'Technology'
AND employeeCode IN (SELECT employeeCode
FROM Employees
GROUP BY employeeCode HAVING COUNT(*) > 1)
The problem is that I want to select employees which are part of the Technology department, but they also participate in other departments.
So, they must be from the Technology department, but they could also be from the Household department. In the database it could look like:
1 | A1 | Alex | Technology
2 | A2 | Thor | Household
3 | A3 | John | Cars
4 | A3 | John | Technology
5 | A4 | Kim | Technology
6 | A4 | Kim | Video Games
So basically the query should return:
A3 | John |
A4 | Kim |
I think it's a small part that I am missing but..
Any ideas on how to filter/sort it so that it always uses the technology and the other departments?
Btw, I tried searching but I couldn't find a problem like mine..
If you want employees that could be in the technology department and another department:
select e.employeeCode, e.employeeName
from employees e
group by e.employeeCode, e.employeeName
having sum(case when e.department = 'Technology' then 1 else 0 end) > 0 and
count(*) > 1;
This assumes no duplicates in the table. If it can have duplicates, then use count(distinct department) > 1 rather than count(*) > 1.
Try this:
SELECT E.employeeCode, E.employeeName
FROM Employees E
INNER JOIN (SELECT DISTINCT E1.employeeCode, E1.employeeName
FROM Employees E
WHERE E.Department = 'Technology'
) AS A ON E.employeeCode = A.employeeCode AND E.employeeName = A.employeeName
GROUP BY E.employeeCode, E.employeeName
HAVING COUNT(*) > 1;
You can use EXISTS with correlated sub-query joining on the same table with different condition.
SELECT e1.employeeCode, e1.employeeName
FROM Employees e1
WHERE e1.Department = 'Technology'
AND EXISTS (SELECT * FROM Employees e2
WHERE e1.employeeCode = e2.employeeCode
AND e2.Department <> 'Technology')
This will work for your case:
SELECT a.employeeCode, a.employeeName
FROM Employees a, Employees b
WHERE
a.Department = 'Technology'
AND
b.Department <> 'Technology'
AND
a.employeeCode = b.employeeCode
AND
a.employeeID <> b.employeeID