Conditional aggregate queries [closed] - sql

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 1 year ago.
Improve this question
I've seen other solutions. Curious if this method of solving these would work.
TABLES:
attendance_events : date | student_id | attendance
all_students : student_id | school_id | grade_level | date_of_birth | hometown
What percent of students attend school on their birthday?
With agg_join as (SELECT att.date as dates, att.attendance as attendance, als.date_of_birth as DOB, att.student_id as student_id
FROM attendance_events att
join all_students als on att.student_id = als.studentid)
Select count(DISTINCT student_id) as total_students,
count( Distinct case when DOB = dates and attendance = TRUE) as count_of_DOBS,
total_students/ count_of_DOBS as percent_of_student
from agg_join
Which grade level had the largest drop in attendance between yesterday and today?
With agg_join as ( SELECT att.date as dates, att.attendance as attendance, als.grade_level as grade
FROM attendance_events att
join all_students als on att.student_id = als.studentid)
Select grade,
case when dates ( 'd', -1, currentdate) and attendance = True then 1
else 0 end as yesterday_att,
case when dates ( 'd', currentdate) and attendance = True then 1
else 0 end as Today_att,
(Today_att - yesterday_att) * -1 AS DIFF
from agg_join
Group by grade
Order by DIFF DESC
Limit 1

What percent of students attend school on their birthday?
SELECT 100.0
* count(*) FILTER (WHERE EXISTS (SELECT FROM attendance_events a
WHERE a.student_id = s.student_id
AND f_mmdd(a.date) = f_mmdd(s.date_of_birth)
))
/ count(*) AS percentage
FROM all_students s;
Where the custom function f_mmdd() is defined here:
How do you do date math that ignores the year?
See:
Convert numeric result to percentage with decimal digits
About the aggregate FILTER clause:
Aggregate columns with additional (distinct) filters
Which grade level had the largest drop in attendance between yesterday and today?
SELECT s.grade_level
, count(*) FILTER (WHERE a.date = CURRENT_DATE - 1) AS yesterday_attendance
, count(*) FILTER (WHERE a.date = CURRENT_DATE) AS today_attendance
FROM attendance_events a
JOIN all_students s USING (student_id)
WHERE a.date IN (CURRENT_DATE, CURRENT_DATE -1) -- logically redundant
GROUP BY s.grade_level
ORDER BY today_attendance - yesterday_attendance
-- short for: yesterday_attendance - today_attendance DESC
LIMIT 1;
WHERE a.date IN (CURRENT_DATE, CURRENT_DATE -1) is logically redundant, but makes the query much faster.
Read up and try to understand why these work, if you are not solving them yourself.

Related

Calculate rolling year totals in sql

I am gathering something that is essentially am "enrollment date" for users. The "enrollment date" is not stored in the database (for a reason too long to explain here), so I have to deduce it from the data. I then want to reuse this CTE in numerous places throughout another query to gather values such as "total orders 1 year before enrollment" and "total orders 1 year after enrollment".
I haven't gotten this code to run, as it's much more complex in my actual data set (this code is paraphrased from the actual code) and I have a feeling it's not the best way to do this. As you can see, my date conditionals are mostly just placeholders, but I think it should be obvious what I am trying to do.
That said, I think this would mostly work. My question is, is there a better way to do this? Additionally, could I combine the rolling year before and rolling year after into one table somehow? (maybe window functions)? This is part of a much bigger query, so the more consolidation I could do, the better it would seem.
For what it's worth, the subquery to derive the "enrollment date" is also more complex than shown here.
With enroll as (Select
user_id,
MIN(date) as e_date
FROM `orders` o
WHERE (subscribed = True)
group by user_id
)
Select*
from users
left join (select
user_id,
SUM(total_paid)
from orders where date > (select enroll.e_date where user_id = user_id) AND date < (select enroll.e_date where user_id = user_id + 365 days)
and order_type = 'special'
group by user_id
) as rolling_year_after on rolling_year_after.user_id = users.user_id
left join (select
user_id,
SUM(total_paid)
from orders where date < (select enroll.e_date where user_id = user_id) and date > (select enroll.e_date where user_id = user_id - 365 days)
and order_type = 'special'
group by user_id
) as rolling_year_before on rolling_year_before.user_id = users.user_id
Maybe something like this, not sure if its more performant, but looks a bit cleaner:
With enroll as (Select
user_id,
MIN(date) as e_date
FROM `orders` o
WHERE (subscribed = True)
group by user_id
)
, rolling_year as (
select
user_id,
SUM(CASE WHEN date between enroll.edate and enroll.edate + 365 days then (total_paid) else 0 end) as rolling_year_after,
SUM(CASE WHEN date between enroll.edate - 365 days and enroll.edate then (total_paid) else 0 end) as rolling_year_before
from orders
left join enroll
on order.user_id = enroll.user_id
where order_type = 'special'
group by user_id
)
Select *
from users
left join rolling_year
on users.user_id = rolling_year.user_id

SQL Rowwise comparison between groups

Question
The following is a snippet of my data:
Create Table Emps(person VARCHAR(50), started DATE, stopped DATE);
Insert Into Emps Values
('p1','2015-10-10','2016-10-10'),
('p1','2016-10-11','2017-10-11'),
('p1','2017-10-12','2018-10-13'),
('p2','2019-11-13','2019-11-13'),
('p2','2019-11-14','2020-10-14'),
('p3','2020-07-15','2021-08-15'),
('p3','2021-08-16','2022-08-16');
db<>fiddle.
I want to use T-SQL to get a count of how many persons fulfil the following criteria at least once - multiples should also count as one:
For a person:
One of the dates in 'started' (say s1) is larger than at least one of the dates in 'ended' (say e1)
s1 and e1 are in the same year, to be set manually - e.g. '2021-01-01' until '2022-01-01'
Example expected response
If I put the date range '2016-01-01' until '2017-01-01' somewhere in a WHERE / HAVING clause, the output should be 1 as only p1 has both a start date and an end date that fall in 2016 where the start date is larger than the end date:
s1 = '2016-10-11', and e1 = '2016-10-10'.
Why can't I do this myself
The reason I'm stuck is that I don't know how to do this rowwise comparison between groups. The question requires comparing values across columns (start with end) across rows, within a person ID.
Use conditional aggregation to get the maximum start date and the minimum stop date in the given range.
select person
from emps
group by person
having max(case when started >= '2016-01-01' and started < '2017-01-01'
then started end) >
min(case when stopped >= '2016-01-01' and stopped < '2017-01-01'
then stopped end);
Demo: https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=45adb153fcac9ce72708f1283cac7833
I would choose to use a self-outer-join with an exists correlation, it should be pretty much the most performant, all things being equal.
select Count(*)
from emps e
where exists (
select * from emps e2
where e2.person = e.person
and e2.stopped > e.started
and e.started between '20160101' and '20170101'
and e2.started between '20160101' and '20170101'
);
You said you plan to set the dates manually, so this works where we set the start date in one CTE, and the end date in another CTE. Then we calculate the min/max for each, and use that criteria in the query where statement.
with min_max_start as (
select person,
min(started) as min_start, --obsolete
max(started) as max_start
from emps
where started >= '2016-01-01'
group by person
),
min_max_end as (
select person,
min(stopped) as min_stop,
max(stopped) as max_stop --obsolete
from emps
where stopped < '2017-01-01'
group by person
)
select count(distinct e.person)
from emps e
join min_max_start mms
on e.person = mms.person
join min_max_end mme
on e.person = mme.person
where mms.max_start> mme.min_stop
Output: 1
Try the following:
With CTE as
(
Select D.person, D.started, T.stopped,
case
when Year(D.started) = Year(T.stopped) and D.started > T.stopped
then 1
else 0
end as chk
From
(Select person, started From Emps Where started >= '2016-01-01') D
Join
(Select person, stopped From Emps Where stopped <= '2017-01-01') T
On D.person = T.person
)
Select Count(Distinct person) as CNT
From CTE
Where chk = 1;
To get the employee list who met the criteria use the following on the CTE instead of the above Select Count... query:
Select person, started, stopped
From CTE
Where chk = 1;
See a demo from db<>fiddle.

More than 1 appointment on the same day for a patient and display both appointments

I am trying to find patients that have more than 1 appointment on the same day. I want to then display all the appointments the patient may have. Do I need to use a subquery to do this? Here is what I have so far:
Select
Appt.ID-PatNm as Patient,
ApptNum,
Sched_ApptType.Prov.Mnemonic as Type,
Appt.Provider-Name as Provider,
Appt.Dt,
Appt.Tm,
Appt.Department-Mnemonic As Dept,
Appt.SchedulerInits,
Case $EXTRACT(Appt.InternalStatus,1)
when 'P' then 'Pending'
when 'A' then 'Arrived'
when 'R' then 'Rescheduled'
End as Status
From Sched.Appointment Appt
JOIN Sched_ApptType.Prov ON
Appt.Department = Sched_ApptType.Prov.Department
and
Appt.Provider = Sched_ApptType.Prov.Provider
and
Appt.Type = Sched_ApptType.Prov.ApptType
Where (Appt.Dt) > DATEADD('DD',-120,CURRENT_DATE)
AND Appt.InternalStatus IN ('P','R','A')
AND Appt.Department-Mnemonic= 'EYE'
Group By
Appt.ID-PatNm,
Appt.Dt
You get the patients having more than one appointment in a day by grouping by patient and day:
select distinct a.id_patnm
from sched.appointment a
group by a.id_patnm, a.dt
having count(*) > 1
So yes, you need a subquery:
Where (Appt.Dt) > DATEADD('DD',-120,CURRENT_DATE)
AND Appt.InternalStatus IN ('P','R','A')
AND Appt.Department_Mnemonic= 'EYE'
AND Appt.ID_PatNm IN
(
select a.id_patnm
from sched.appointment a
group by a.id_patnm, a.dt
having count(*) > 1
)
(BTW: I used id_patnm instead of id-patnm here, for I don't know any DBMS that would allow the hyphen. When using a hyphen in a column name you have to use quotes on the name, e.g. "id-patnm".)
I suppose you could add a column for Appointment_id which would then allow you to get the desired result.

Trouble running a complex query in sql?

I am pretty new to SQL Server and just started playing with it. I am trying to create a table that shows attendance percentage by department.
So first i run this query:
SELECT CrewDesc, COUNT(*)
FROM database.emp
INNER JOIN database.crew on sim1 = sim2
GROUP BY CrewDesc
This gives a table like this:
Accounting 10
Marketing 5
Economics 20
Engineering 5
Machinery 5
Tech Support 10
Then i run another query:
SELECT DeptDescription, COUNT(*)
FROM database.Attendee
GROUP BY DeptDescription
This gives me the result of all the people that have attended meeting something like
Accounting 8
Marketing 5
Economics 15
Engineering 10
Tech Support 8
Then I get the current week in the year by SELECT Datepart(ww, GetDate()) as CurrentWeek To make this example easy lets assume this will be week "2".
Now the way i was going to create this was a table for each step but that seems like waste. Is there a way we can combine to tables in a query? So in the end result i would like a table like this
Total# Attd Week (Total*Week) Attd/(Total*week)%
Accounting 10 8 2 20 8/20
Marketing 5 5 2 10 5/10
Economics 20 15 2 40 15/40
Engineering 5 10 2 10 10/10
Machinery 5 NULL 2 10 0/10
Tech Support 10 8 2 20 8/20
Ok, note that my recommendation below is based on your exact existing queries - there are certainly other ways to construct this that may be more performant, but functionally this should work for your requirement. Also, it illustrates the key features of different join types that happen to be relevant for your request, as well as inline views (aka nested queries), which are a super-powerful technique in the SQL language as a whole.
select t1.CrewDesc, t1.Total, t2.Attd, t3.Week,
(t1.Total*t3.Week) as Total_x_Week,
case when isnull(t1.Total*t3.Week, 0) = 0 then 0 else isnull(t2.Attd, 0) / isnull(t1.Total*t3.Week, 0) end as PercentageAttd
from (
SELECT CrewDesc, COUNT(*) AS Total
FROM database.emp INNER JOIN database.crew on sim1 = sim2
GROUP BY CrewDesc
) t1
left outer join /* left outer to keep all rows from t1 */ (
SELECT DeptDescription, COUNT(*) AS Attd
FROM database.Attendee GROUP BY DeptDescription
) t2
on t1.CrewDesc = t2.DeptDescription
cross join /* useful when adding a scalar value to all rows */ (
SELECT Datepart(ww, GetDate()) as Week
) t3
order by t1.CrewDesc
Good luck!
Try something like this
SELECT COALESCE(a.crewdesc,b.deptdescription),
a.total,
b.attd,
Datepart(ww, Getdate()) AS week,
total * Datepart(ww, Getdate()),
b.attd/(a.total*Datepart(ww, Getdate()))
FROM (query 1) a
FULL OUTER JOIN (query 2) b
ON a.crewdesc = b.deptdescription
WITH Total AS ( SELECT CrewDesc, COUNT(*) AS [Count]
FROM database.emp
INNER JOIN database.crew on sim1 = sim2
GROUP BY CrewDesc
),
Attd AS ( SELECT DeptDescription, COUNT(*) AS [Count]
FROM database.Attendee
GROUP BY DeptDescription
)
SELECT COALESCE(CrewDesc,DeptDescription) AS [Dept],
Total.[Count] AS [Total#],Attd.[Count] AS [Attd],
Total.[Count] * Datepart(ww, GetDate()) AS [(Total*Week)],
CAST(Attd.[Count] AS VARCHAR(10))+'/'+ CAST((Total.[Count] * Datepart(ww, GetDate()))AS VARCHAR(10)) AS [Attd/(Total*week)%]
FROM Total INNER JOIN Attd ON Total.CrewDesc = Attd.DeptDescription
I'm assuming your queries are correct -- you give no real information about your model so I've no way to know. They look wrong since the same data is called CrewDesc in one table and Dept in another. Also the join sim1 = sim2 seems very strange to me. In any case given the queries you posted this will work.
With TAttend as
(
SELECT CrewDesc, COUNT(*) as TotalNum
FROM database.emp
INNER JOIN database.crew on sim1 = sim2
GROUP BY CrewDesc
), Attend as
(
SELECT DeptDescription, COUNT(*) as Attd
FROM database.Attendee
GROUP BY DeptDescription
)
SELECT CrewDesc as Dept, TotalNum, ISNULL(Attd, 0) as Attd ,Datepart(ww, GetDate()) as Week,
CASE WHEN ISNULL(Attd, 0) > 0 THEN 0
ELSE ISNULL(Attd, 0) / (TotalNum * Datepart(ww, GetDate()) ) END AS Percent
FROM TAttend
LEFT JOIN Attend on CrewDesc = DeptDescription

Group by the Sub-Query which is derived from WITH CLAUSE [duplicate]

This question already has answers here:
ORA-00979 not a group by expression
(10 answers)
Closed 7 years ago.
I'm getting error for my below query, while I'm trying to grouping the sub-query. I built the sub-query using the WITH Clause.
Please correct me
WITH Student AS (SELECT * FROM CLASS WHERE SEX='M')
SELECT NAME, AGE, STATUS, SUM(TOTAL)
(SELECT
NAME,
'15' AS AGE,
CASE WHEN ATTENDANCE > 50 AND ATTENDANCE < 60 THEN 'GOOD'
WHEN ATTENDANCE > 60 'GREAT'
ELSE 'BAD' END AS STATUS
SUM (MARK) AS TOTAL
FROM STUDENT
GROUP BY NAME, ATTENDANCE ) A
GROUP BY NAME, AGE, STATUS
Error: SQL Query not properly ended
I think you are missing a from clause:
WITH Student AS (SELECT * FROM CLASS WHERE SEX='M')
SELECT NAME, AGE, STATUS, SUM(TOTAL)
FROM (SELECT NAME, '15' AS AGE,
(CASE WHEN ATTENDANCE > 50 AND ATTENDANCE < 60 THEN 'GOOD'
WHEN ATTENDANCE > 60 'GREAT'
ELSE 'BAD'
END) AS STATUS,
SUM(MARK) AS TOTAL
FROM STUDENT
GROUP BY NAME, ATTENDANCE
) A
GROUP BY NAME, AGE, STATUS
The query is much longer than it needs to be to achieve the output you want, but this seems to be the problem you have.