I want to count future Appointments made on the same day of an active appointment by Location. I expect multiple counts per Patient_ID given a date range. I am not sure if I need a temp table or if a subquery would work.
From the code below this is the error I get:
Subquery returned more than 1 value. This is not permitted when the
subquery follows =, !=, <, <= , >, >= or when the subquery is used as
an expression.
Definitions:
Appointment_DateTime - (Date) is the actual appointment event
DateTime_Scheduled - (Date) is the logging timestamp of future appointments
Description - (text) is the Location Description
Patient_ID - (int) is the unique patient ID
Appointment_ID - (int) is the unique Appointment ID
SQL
SELECT
loc.Description
,Count(app.Appointment_ID)
FROM [Ntier_HARH].[PM].[Appointments] app
join [Ntier_HARH].[PM].[Resources] res
on res.Resource_ID = app.Resource_ID
join [Ntier_HARH].[PM].[Practitioners] doc
on doc.Practitioner_ID = res.Practitioner_ID
join [Ntier_HARH].[PM].[Scheduling_Locations] loc
on loc.Scheduling_Location_ID = app.Scheduling_Location_ID
where
cast(app.DateTime_Scheduled as date) = '2017-01-16'
and app.status <> 'X'
and cast(app.Appointment_DateTime as date) =
(Select cast(DateTime_Scheduled as date)
from [Ntier_HARH].[PM].[Appointments]
where Patient_ID = app.Patient_ID)
group by loc.Description
You may use in instead of =
where
cast(app.DateTime_Scheduled as date) = '2017-01-16'
and app.status <> 'X'
and cast(app.Appointment_DateTime as date) IN (Select cast(DateTime_Scheduled as date) from [Ntier_HARH].[PM].[Appointments] where Patient_ID = app.Patient_ID)
group by loc.Description
Don't you also need to group by the PatientId? If you want the count of appointments by location only, then the subquery isn't necessary. I don't see why the other two tables are necessary either.
SELECT l.Description, Count(a.Appointment_ID)
FROM [Ntier_HARH].[PM].[Appointments] a
join [Ntier_HARH].[PM].[Scheduling_Locations] l
on l.Scheduling_Location_ID = a.Scheduling_Location_ID
where cast(a.DateTime_Scheduled as date) = '2017-01-16'
and a.status <> 'X'
group by l.Description
Related
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.
I am trying to get a count of all of yesterdays rows. The query i have runs good but does not pick up null values. Is there a way i can query a count of null and non null values?
Here is my code:
SELECT dateadd(day,datediff(day,0,GETDATE())-1,0) as Received_Date,
COUNT(*) as Enrollments_Completed
FROM Table CD,
CCMDB.dbo.ResolutionLetterDetails RD
WHERE CD.ccid = RD.ccid
and CompletedDate >= DATEADD(d,DATEDIFF(d,1,getdate()),0)
and CompletedDate < DATEADD(d,DATEDIFF(d,0,getdate()),0)
AND CatID in('cat0014')
AND IncomingType <> 'RITS'
AND status = 'Completed'
Convert your CompletedDate to a date with no time and make it equal yesterdays date with no time (from GETDATE()) and use correct JOIN code.
SELECT dateadd(day,datediff(day,0,GETDATE())-1,0) as Received_Date,
COUNT(*) as Enrollments_Completed
FROM Table CD
LEFT JOIN CCMDB.dbo.ResolutionLetterDetails RD ON CD.ccid = RD.ccid
WHERE dateadd(day,datediff(day,1,CompletedDate),0) = dateadd(day,datediff(day,1,GETDATE()),0)
AND CatID IN ('cat0014')
AND IncomingType != 'RITS'
AND status = 'Completed'
Return NULLs:
SELECT dateadd(day,datediff(day,0,GETDATE())-1,0) as Received_Date,
COUNT(*) as Enrollments_Completed
FROM Table CD
LEFT JOIN CCMDB.dbo.ResolutionLetterDetails RD ON CD.ccid = RD.ccid
WHERE dateadd(day,datediff(day,1,CompletedDate),0) = dateadd(day,datediff(day,1,GETDATE()),0)
AND (CatID IN ('cat0014') OR CatID IS NULL)
AND (IncomingType != 'RITS' OR IncomingType IS NULL)
AND (status = 'Completed' OR status IS NULL)
I would fix your query and do:
SELECT CAST(DATEADD(day, -1, GETDATE()) as DATE) as Received_Date,
COUNT(*) as Enrollments_Completed
FROM Table CD JOIN
CCMDB.dbo.ResolutionLetterDetails RD
ON CD.ccid = RD.ccid
WHERE CompletedDate >= CAST(DATEADD(day, -1, GETDATE()) as DATE) AND
CompletedDate < CAST(GETDATE() as DATE) AND
CatID IN ('cat0014') AND
IncomingType <> 'RITS' AND
status = 'Completed';
For the date part, you could also do:
CAST(CompletedDate as DATE) = CAST(DATEADD(day, -1, GETDATE()) as DATE)
This version is even index-safe in SQL Server (although not necessarily in other databases).
Notes:
The DATE data type considerably simplifies your calculations.
Never use commas in the FROM clause. Always use proper, explicit, standard JOIN syntax.
You should qualify all column names so you (and anyone reading the query) knows what table the column comes from.
I'd like to apply a WHERE clause to just one field of my select query. The internet told me to use CASE WHEN in the line where I'm selecting my fields and to then remove the where clause. But I was then told that my "selected non aggregate values must be part of the associated group."
The original query looked like this:
SELECT
CAST(EVENT_TIMESTAMP AS DATE) AS Date1,
COUNT(DISTINCT EMAIL) END AS Subs,
SUM(DWELL_MINUTES) AS Dwell
FROM VwNIMEventFct
INNER JOIN VwNIMUserDim ON VwNIMUserDim.NIM_USER_ID = VwNIMEventFct.NIM_USER_ID
INNER JOIN TmpNIMSalesForceDB ON VwNIMUserDim.USER_EMAIL_ADDRESS = EMAIL
WHERE Date1 >= '2013-11-01'
// The problem is here, in the AND clause
AND (SUBSCRIPTION_END_DATE > VwNIMEventFct.EVENT_TIMESTAMP OR SUBSCRIPTION_END_DATE
IS NULL)
GROUP BY Date1
ORDER BY Date1
I then changed the query after doing some searching to this:
SELECT
CAST(EVENT_TIMESTAMP AS DATE) AS Date1,
CASE WHEN (SUBSCRIPTION_END_DATE > Date1 OR SUBSCRIPTION_END_DATE IS NULL)
THEN COUNT(DISTINCT TmpNIMSalesForceDB.EMAIL) END AS Subs,
SUM(VwNIMEventFct.DWELL_MINUTES) AS Dwell
FROM RDMAVWSANDBOX.VwNIMEventFct
INNER JOIN VwNIMUserDim ON VwNIMUserDim.NIM_USER_ID = VwNIMEventFct.NIM_USER_ID
INNER JOIN TmpNIMSalesForceDB ON VwNIMUserDim.USER_EMAIL_ADDRESS = EMAIL
WHERE Date1 >= '2013-11-01'
GROUP BY Date1
ORDER BY Date1
I'd like to select:
1) the date as per "Date1" in the query, then,
2) for each date, the count of distinct email addresses where the SUBSCRIPTION_END_DATE is either NULL or in the future (greater than Date1),
3) Sum of a field (I'm fine here)
How do I do number 2?
EDIT based on answer:
Does this part of the select query ignore and thus not count blank records when
SUBSCRIPTION_END_DATE is null?
SELECT
COUNT(DISTINCT CASE WHEN TmpNIMSalesForceDB.SUBSCRIPTION_END_DATE > Date1 OR TmpNIMSalesForceDB.SUBSCRIPTION_END_DATE IS NULL
THEN TmpNIMSalesForceDB.EMAIL END) AS Subs,
I need to count all records where SUBSCRIPTION_END_DATE is blank/null or where those dates are after Date1.
You need to put your CASE statement inside the COUNT, rather than vice versa, as it needs to be evaluated for each row (which case should this row fall in) and then aggregated across each group (how many rows in that group fell in the non-null group).
COUNT(DISTINCT CASE WHEN (SUBSCRIPTION_END_DATE > Date1 OR SUBSCRIPTION_END_DATE IS NULL)
THEN TmpNIMSalesForceDB.EMAIL END) AS Subs
The COUNT will ignore the NULLs implicitly left by the lack of an ELSE clause in the CASE statement, thus counting only the distinct EMAIL values from rows which met the condition.
Put the case statement inside of the count function.
SELECT
CAST(EVENT_TIMESTAMP AS DATE) AS Date1,
COUNT(DISTINCT(CASE
WHEN SUBSCRIPTION_END_DATE > Date1 OR SUBSCRIPTION_END_DATE IS NULL
THEN TmpNIMSalesForceDB.EMAIL END)) AS Subs,
SUM(VwNIMEventFct.DWELL_MINUTES) AS Dwell
FROM RDMAVWSANDBOX.VwNIMEventFct
INNER JOIN VwNIMUserDim
ON VwNIMUserDim.NIM_USER_ID = VwNIMEventFct.NIM_USER_ID
INNER JOIN TmpNIMSalesForceDB
ON VwNIMUserDim.USER_EMAIL_ADDRESS = EMAIL
WHERE Date1 >= '2013-11-01'
GROUP BY Date1
ORDER BY Date1
I have an order file, with order id and ship date. Orders can only be shipped monday - friday. This means there are no records selected for Saturday and Sunday.
I use the same order file to get all order dates, with date in the same format (yyyymmdd).
i want to select a count of all the records from the order file based on order date... and (i believe) full outer join (or maybe right join?) the date file... because i would like to see
20120330 293
20120331 0
20120401 0
20120402 920
20120403 430
20120404 827
etc...
however, my sql statement is still not returning a zero record for the 31st and 1st.
with DatesTable as (
select ohordt "Date" from kivalib.orhdrpf
where ohordt between 20120315 and 20120406
group by ohordt order by ohordt
)
SELECT ohscdt, count(OHTXN#) "Count"
FROM KIVALIB.ORHDRPF full outer join DatesTable dts on dts."Date" = ohordt
--/*order status = filled & order type = 1 & date between (some fill date range)*/
WHERE OHSTAT = 'F' AND OHTYP = 1 and ohscdt between 20120401 and 20120406
GROUP BY ohscdt ORDER BY ohscdt
any ideas what i'm doing wrong?
thanks!
It's because there is no data for those days, they do not show up as rows. You can use a recursive CTE to build a contiguous list of dates between two values that the query can join on:
It will look something like:
WITH dates (val) AS (
SELECT CAST('2012-04-01' AS DATE)
FROM SYSIBM.SYSDUMMY1
UNION ALL
SELECT Val + 1 DAYS
FROM dates
WHERE Val < CAST('2012-04-06' AS DATE)
)
SELECT d.val AS "Date", o.ohscdt, COALESCE(COUNT(o.ohtxn#), 0) AS "Count"
FROM dates AS d
LEFT JOIN KIVALIB.ORDHRPF AS o
ON o.ohordt = TO_CHAR(d.val, 'YYYYMMDD')
WHERE o.ohstat = 'F'
AND o.ohtyp = 1
I need to analyze some weblogs and determine if a user has visited once, taken a year break, and visited again. I want to add a flag to every row (Y/N) with a VisitId that meets the above criteria.
How would I go about creating this sql?
Here are the fields I have, that I think need to be used (by analyzing the timestamp of the first page of each visit):
VisitID - each visit has a unique Id (ie. 12356, 12345, 16459)
UserID - each user has one Id (ie. steve = 1, ted = 2, mark = 12345, etc...)
TimeStamp - looks like this: 2010-01-01 00:32:30.000
select VisitID, UserID, TimeStamp from page_view_t where pageNum = 1;
thanks - any help would be greatly appreciated.
You could rank every user's rows, then join the ranked row set to itself to compare adjacent rows:
;
WITH ranked AS (
SELECT
*,
rnk = ROW_NUMBER() OVER (PARTITION BY UserID ORDER BY TimeStamp)
FROM page_view_t
),
flagged AS (
SELECT
*,
IsReturnVisit = CASE
WHEN EXISTS (
SELECT *
FROM ranked
WHERE UserID = r.UserID
AND rnk = r.rnk - 1
AND TimeStamp <= DATEADD(YEAR, -1, r.TimeStamp)
)
THEN 'Y'
ELSE 'N'
END
FROM ranked r
)
SELECT
VisitID,
UserID,
TimeStamp,
IsReturnVisit
FROM flagged
Note: the above flags only return visits.
UPDATE
To flag the first visits same as return visits, the flagged CTE could be modified as follows:
…
SELECT
*,
IsFirstOrReturnVisit = CASE
WHEN p.UserID IS NULL OR r.TimeStamp >= DATEADD(YEAR, 1, p.TimeStamp)
THEN 'Y'
ELSE 'N'
END
FROM ranked r
LEFT JOIN ranked p ON r.UserID = p.UserID AND r.rnk = p.rnk + 1
…
References that might be useful:
WITH common_table_expression (Transact-SQL)
Ranking Functions (Transact-SQL)
ROW_NUMBER (Transact-SQL)
The other guy was faster but since I took time to do it and it's a completely different approach I might as well post It :D.
SELECT pv2.VisitID,
pv2.UserID,
pv2.TimeStamp,
CASE WHEN pv1.VisitID IS NOT NULL
AND pv3.VisitID IS NULL
THEN 'YES' ELSE 'NO' END AS IsReturnVisit
FROM page_view_t pv2
LEFT JOIN page_view_t pv1 ON pv1.UserID = pv2.UserID
AND pv1.VisitID <> pv2.VisitID
AND (pv1.TimeStamp <= DATEADD(YEAR, -1, pv2.TimeStamp)
OR pv2.TimeStamp <= DATEADD(YEAR, -1, pv1.TimeStamp))
AND pv1.pageNum = 1
LEFT JOIN page_view_t pv3 ON pv1.UserID = pv3.UserID
AND (pv3.TimeStamp BETWEEN pv1.TimeStamp AND pv2.TimeStamp
OR pv3.TimeStamp BETWEEN pv2.TimeStamp AND pv1.TimeStamp)
AND pv3.pageNum = 1
WHERE pv2.pageNum = 1
Assuming page_view_t table stores UserID and TimeStamp details of each visit of the user, the following query will return users who have visited taking a break of at least an year (365 days) between two consecutive visits.
select t1.UserID
from page_view_t t1
where (
select datediff(day, max(t2.[TimeStamp]), t1.[TimeStamp])
from page_view_t t2
where t2.UserID = t1.UserID and t2.[TimeStamp] < t1.[TimeStamp]
group by t2.UserID
) >= 365