I am trying to make a report that will determine if any clients (patient_id) have a duplicate appointment in our system. I have the fields of proc_chron (start time down to the second), proc_chron_end (end time down to the second), and proc_duration. Thank you in advance for any help.
select
patient_id,
attending_id,
proc_duration,
proc_chron,
proc_chron_end
from patient_clin_tran
where place_of_service not in ('23', '24', '25', '26')
and (proc_chron between '2015-06-01' and '2015-09-01')
and billing_proc_code not in ('BHHMTH')
On way would be to add a exists predicate with a correlated subquery in the where clause that limits the result to those appointments that overlap.
select
patient_id,
attending_id,
proc_duration,
proc_chron,
proc_chron_end
from patient_clin_tran p -- notice the table alias
where place_of_service not in ('23', '24', '25', '26')
and (proc_chron between '2015-06-01' and '2015-09-01')
and billing_proc_code not in ('BHHMTH')
and case when exists (
select 1 from patient_clin_tran
where patient_id = p.patient_id
and attending_id <> p.attending_id
and p.proc_chron < proc_chron_end
and p.proc_chron_end > proc_chron
) then 1 else 0 end = 1
order by p.patient_id, p.attending_id;
Sample SQL Fiddle with some imaginary data.
This only checks for overlapping appointments with different attending_id. If you want to check for overlaps with the same attending then you would have to remove the and attending_id <> p.attending_id and instead add a condition that uniquely identifies each row so that appointments don't overlap with themselves.
This would tell you where you have over lapping appointments:
;WITH cte AS (
SELECT
patient_id
,attending_id
,proc_duration
,proc_chron
,proc_chron_end
FROM patient_clin_tran
WHERE place_of_service NOT IN ('23', '24', '25', '26')
AND proc_chron BETWEEN '2015-06-01' AND '2015-09-01'
AND billing_proc_code NOT IN ('BHHMTH'))
SELECT c1.*
FROM cte c1
INNER JOIN cte c2 ON c1.patient_id = c2.patient_id
WHERE c2.proc_chron BETWEEN c1.proc_chron AND c1.proc_chron_end
AND c2.proc_chron_end BETWEEN c1.proc_chron AND c1.proc_chron_end
AND c1.attending_id != c2.attending_id
All I did was wrap your query in a common table expression and the join it with itself on the patient ID. The where clause then filters only appoints that overlap excluding the identical appointment.
Related
I need to group data together that are related to each other by overlapping timespans based on the records start and end times. SQL-fiddle here: http://sqlfiddle.com/#!18/87e4b/1/0
The current query I have built is giving incorrect results. Callid 3 should give a callCount of 4. It does not because record 6 is not included since it does not overlap with 3, but should be included because it does overlap with one of the other related records. So I believe a recursive CTE may be in need but I am unsure how to write this.
Schema:
CREATE TABLE Calls
([callid] int, [src] varchar(10), [start] datetime, [end] datetime, [conf] varchar(5));
INSERT INTO Calls
([callid],[src],[start],[end],[conf])
VALUES
('1','5555550001','2019-07-09 10:00:00', '2019-07-09 10:10:00', '111'),
('2','5555550002','2019-07-09 10:00:01', '2019-07-09 10:11:00', '111'),
('3','5555550011','2019-07-09 11:00:00', '2019-07-09 11:10:00', '111'),
('4','5555550012','2019-07-09 11:00:01', '2019-07-09 11:11:00', '111'),
('5','5555550013','2019-07-09 11:01:00', '2019-07-09 11:15:00', '111'),
('6','5555550014','2019-07-09 11:12:00', '2019-07-09 11:16:00', '111'),
('7','5555550014','2019-07-09 15:00:00', '2019-07-09 15:01:00', '111');
Current query:
SELECT
detail_record.callid,
detail_record.conf,
MIN(related_record.start) AS sessionStart,
MAX(related_record.[end]) As sessionEnd,
COUNT(related_record.callid) AS callCount
FROM
Calls AS detail_record
INNER JOIN
Calls AS related_record
ON related_record.conf = detail_record.conf
AND ((related_record.start >= detail_record.start
AND related_record.start < detail_record.[end])
OR (related_record.[end] > detail_record.start
AND related_record.[end] <= detail_record.[end])
OR (related_record.start <= detail_record.start
AND related_record.[end] >= detail_record.[end])
)
WHERE
detail_record.start > '1/1/2019'
AND detail_record.conf = '111'
GROUP BY
detail_record.callid,
detail_record.start,
detail_record.conf
HAVING
MIN(related_record.start) >= detail_record.start
ORDER BY sessionStart DESC
Expected Results:
callid conf sessionStart sessionEnd callCount
7 111 2019-07-09T15:00:00Z 2019-07-09T15:01:00Z 1
3 111 2019-07-09T11:00:00Z 2019-07-09T11:15:00Z 4
1 111 2019-07-09T10:00:00Z 2019-07-09T10:11:00Z 2
This is a gaps-and-islands problem. It does not require a recursive CTE. You can use window functions:
select min(callid), conf, grouping, min([start]), max([end]), count(*)
from (select c.*,
sum(case when prev_end < [start] then 1 else 0 end) over (order by start) as grouping
from (select c.*,
max([end]) over (partition by conf order by [start] rows between unbounded preceding and 1 preceding) as prev_end
from calls c
) c
) c
group by conf, grouping;
The innermost subquery calculates the previous end. The middle subquery compares this to the current start, to determine when groups of adjacent rows are the beginning of a new group. A cumulative sum then determines the grouping.
And, the outer query aggregates to summarize information about each group.
Here is a db<>fiddle.
The table structure:
StaffingRecords
PersonnelId int
GroupId int
StaffingStartDateTime datetime
StaffingEndDateTime datetime
How can I get a list of staffing records, given a date and a group id that employees belong to, where the count of present employees fell below a threshold, say, 3, at any minute of the day?
The way my brain works, I would call a stored proc repeatedly with each minute of the day, but of course this would be horribly inefficient:
SELECT COUNT(PersonnelId)
FROM DailyRosters
WHERE GroupId=#GroupId
AND StaffingStartTime <= #TimeParam
AND StaffingEndTime > #TimeParam
AND COUNT(GroupId) < 3
GROUP BY GroupId
HAVING COUNT(PersonnelId) < 3
Edit: If it helps to refine the question, employees may come and go throughout the day. Personnel may have a staffing record from 0800 - 0815, and another from 1000 - 1045, for example.
Here is a solution where I find all of the distinct start and end times, and then query to see how many other people are clocked in at the time. Everytime the answer is less than 4, you know you are understaffed at that time, and presumably until the NEXT start time.
with meaningfulDtms(meaningfulTime, timeType, group_id)
as
(
select distinct StaffingStartTime , 'start' as timeType, group_id
from DailyRosters
union
select distinct StaffingEndTime , 'end' as timeType, group_id
from DailyRosters
)
select COUNT(*), meaningfulDtms.group_id, meaningfulDtms.meaningfulTime
from DailyRosters dr
inner join meaningfulDtms on dr.group_id = meaningfulDtms.group_id
and (
(dr.StaffingStartTime < meaningfulDtms.meaningfulTime
and dr.StaffingEndTime >= meaningfulDtms.meaningfulTime
and meaningfulDtms.timeType = 'start')
OR
(dr.StaffingStartTime <= meaningfulDtms.meaningfulTime
and dr.StaffingEndTime > meaningfulDtms.meaningfulTime
and meaningfulDtms.timeType = 'end')
)
group by meaningfulDtms.group_id, meaningfulDtms.meaningfulTime
having COUNT(*) < 4
Create a table with all minutes in the day with dt at PK
It will have 1440 rows
this will not give you count of zero - no staff
select allMiuntes.dt, worktime.grpID, count(distinct(worktime.personID))
from allMinutes
join worktime
on allMiuntes.dt > worktime.start
and allMiuntes.dt < worktime.end
group by allMiuntes.dt, worktime.grpID
having count(distinct(worktime.personID)) < 3
for times with zero I think the best way is a master of grpID
but I am not sure about this one
select allMiuntes.dt, grpMaster.grpID, count(distinct(worktime.personID))
from grpMaster
cross join allMinutes
left join worktime
on allMiuntes.dt > worktime.start
and allMiuntes.dt < worktime.end
and worktime.grpID = grpMaster.grpID
group by allMiuntes.dt, grpMaster.grpID
having count(distinct(worktime.personID)) < 3
I have the following query:
SELECT
FileNumber,
dbo.GetLocalDateTimeFunc(SentDate) AS SentDate
INTO #tmp1
FROM FileMain f
JOIN FileActions fa ON f.FileID = fa.FileID
WHERE ActionDefID = 15 AND SentDate IS NOT NULL
SELECT
FileNumber,
dbo.GetLocalDateTimeFunc(ReceivedDate) AS ReceivedDate
INTO #tmp2
FROM FileMain f
JOIN FileActions fa ON f.FileID = fa.FileID
WHERE ActionDefID = 23 AND ReceivedDate IS NOT NULL
SELECT DISTINCT
o.Name AS Company, fm.FileNumber, pc.Name as Client,
p.State, c.County, t1.SentDate, t2.ReceivedDate,
(SELECT sum(case
when dateadd(day, datediff(day, 0, t1.SentDate), 0) = dateadd(day, datediff(day, 0, t2.ReceivedDate), 0) then
datediff(second, t1.SentDate, t2.ReceivedDate)
when [DATE] = dateadd(day, datediff(day, 0, t1.SentDate), 0) then
case
when t1.SentDate > [DATE] + begin_time then datediff(second, t1.SentDate, [DATE] + end_time)
else duration
end
when [DATE] = dateadd(day, datediff(day, 0, t2.ReceivedDate), 0) then
case
when t2.ReceivedDate < [DATE] + end_time then datediff(second, [DATE] + begin_time, t2.ReceivedDate)
else duration
end
else duration
end
)
/ 60.0 / 60.0
FROM F_TABLE_DATE(t1.SentDate, t2.ReceivedDate) d
INNER JOIN Unisource_Calendar c ON d.WEEKDAY_NAME_LONG = c.day_name)
FROM Office o
JOIN PartnerCompany pc ON o.OfficeID = pc.OfficeID
JOIN FileMain fm ON o.OfficeID = fm.OfficeID AND pc.PartnerCompanyID = fm.ClientID
JOIN Property p ON p.FileID = fm.FileID
JOIN County c ON p.CountyID = c.CountyID
JOIN FileActions fa ON fm.FileID = fa.FileID
JOIN #tmp1 t1 ON t1.FileNumber = fm.FileNumber
JOIN #tmp2 t2 ON t2.FileNumber = fm.FileNumber
WHERE p.State IN ('AR', 'CA', 'CO', 'DE', 'DC', 'FL', 'GA', 'IL', 'IN', 'IA', 'KS', 'KY', 'LA', 'MD', 'MA', 'MI', 'MN', 'MS', 'MO', 'MT', 'NE', 'NJ', 'NV', 'NH', 'NY', 'NC', 'ND', 'OH', 'OK', 'PA', 'RI', 'SC', 'TN', 'TX', 'VA', 'WV', 'WI')
ORDER BY SentDate, FileNumber DESC
I'm getting the following error on my subquery:
Multiple columns are specified in an aggregated expression containing an outer reference. If an expression being aggregated contains an outer reference, then that outer reference must be the only column referenced in the expression.
Does anybody know how to fix this?
Or if someone has a function that can calculate datetime differences while excluding business hours and weekends that would help also. Thanks!
I would recommend you to simplify your code using CTEs for a start (enumeration of ALL tables distracts to give a precise statement). Also you should try your aggregate SUM function as a part of PARTITION by expression. This would probably help to avoid the problem you mentioned.
From what I can glean, the table function F_Table_Date is returning DATE or DATETIME rows for each day between the two parameters, and the UnisourceCalendar Is likely a list of work days (to allot for holidays as you mentioned). If this is the case, and UnisourceCalendar also returns a DATE or DATETIME column, consider this for your subquery:
SELECT (COUNT(*) * 60*60*24)
+ (
SELECT COUNT(*)
FROM UnisourceCalendar
WHERE [DATE] = CAST(CONVERT(VARCHAR,t1.SentDate+1,112) AS DATETIME)
)*DATEDIFF(SS,t1.SentDate,CAST(CONVERT(VARCHAR,t1.SentDate+1,112) AS DATETIME))
+ (
SELECT COUNT(*)
FROM UnisourceCalendar
WHERE [DATE] = CAST(CONVERT(VARCHAR,t1.SentDate+1,112) AS DATETIME)
)*DATEDIFF(SS,CAST(CONVERT(VARCHAR,t2.ReceivedDate,112) AS DATETIME),t2.ReceivedDate)
FROM UnisourceCalendar C
WHERE C.[DATE] > t1.SentDate AND C.[DATE] < t2.ReceivedDate
GROUP BY t1.SentDate, t2.ReceivedDate
What's at Play here:
Presuming 1 row per business day from UnisourceCalendar, any other join is superfluous.
A count is all that's needed, then.
The datediff of a converted/cast value of one date against itself using style 112 strips the time out and is recast as midnight, thus allowing us to get the seconds to next midnight from the sent date, and from the previous midnight of the received date, but only if each date is in the unisource calendar (mulitply by count, if 0, then no seconds added, if 1, then add the extra seconds).
Output is presuming that you will be dividing the results down to hours outside the subquery as you are.
Complicated? Sure, but it should output the results you're looking for in relatively short order.
I need to get an active count of patients who have been discharged in a given time frame, but here is the trick. In this table patients can show up more than once, our system uses an episode based system .
Like in (picture 1).
select
p.patient_id,
p.episode_id,
p.case_status,
p.case_substatus,
p.episode_close_date
from patient p
I need no patients to show up in both of these query's.
select * from patient p
where p.case_status = 'a'
-
select * from patient p
where (p.episode_close_date between '2013-01-01 00:00:00.000' and '2013-06-01 00:00:00.000') and p.case_status = 'i'
I guess, what's the best way to do it, would be that that the highest p.episode_id = 'I'. Any ideas on how to do this?
Thanks in advance.
You can do this with aggregation and a having clause. The having clause counts the number of rows that match each condition -- and you want to set the values to 0 because you want both to return no rows:
select patient_id
from patient p
group by patient_id
having sum(case when p.case_status = 'a' then 1 else 0 end) = 0 and
sum(case when (p.episode_close_date between '2013-01-01 00:00:00.000' and '2013-06-01 00:00:00.000') and
p.case_status = 'i'
then 1 else 0
end) = 0;
Basically, the logic in your first two queries in moved into the separate clauses of the having, to count the rows that match each condition.
EDIT:
Here is how you can see the last episode for each patient:
select p.*
from (select p.*,
max(episode_id) over (partition by patient_id) as maxei
from patients p
) p
where episode_id = maxei;
You may also be able to use this with your logic, but I'm not sure about the interplay between the statuses and the dates in the query.
I have a set of Meeting rooms and meetings in that having start date and end Date. A set of meeting rooms belong to a building.
The meeting details are kept in MeetingDetail table having a startDate and endDate.
Now I want to fire a report between two time period say reportStartDate and reportEndDate, which finds me the time slots in which all the meeting rooms are booked for a given building
Table structure
MEETING_ROOM - ID, ROOMNAME, BUILDING_NO
MEETING_DETAIL - ID, MEETING_ROOM_ID, START_DATE, END_DATE
The query has to be fired for reportStartDate and REportEndDate
Just to clarify further, the aim is to find all the time slots in which all the meeting rooms were booked in a given time period of reportStartDate and reportEndDate
For SQL Server 2005+ you could try the following (see note at the end for mysql)
WITH TIME_POINTS (POINT_P) AS
(SELECT DISTINCT START_DATE FROM MEETING_DETAIL
WHERE START_DATE > #reportStartDate AND START_DATE < #reportEndDate
UNION SELECT DISTINCT END_DATE FROM MEETING_DETAIL
WHERE END_DATE > #reportStartDate AND END_DATE < #reportEndDate
UNION SELECT #reportEndDate
UNION SELECT #reportStartDate),
WITH TIME_SLICE (START_T, END_T) AS
(SELECT A.POINT_P, MIN(B.POINT_P) FROM
TIMEPOINTS A
INNER JOIN TIMEPOINTS B ON A.POINT_P > B.POINT_P
GROUP BY A.POINT_P),
WITH SLICE_MEETINGS (START_T, END_T, MEETING_ROOM_ID, BUILDING_NO) AS
(SELECT START_T, END_T, MEETING_ROOM_ID, BUILDING_NO FROM
TIME_SLICE A
INNER JOIN MEETING_DETAIL B ON B.START_DATE <= A.START_T AND B.END_DATE >= B.END_T
INNER JOIN MEETING_ROOM C ON B.MEETING_ROOM_ID = C.ID),
WITH SLICE_COUNT (START_T, END_T, BUILDING_NO, ROOMS_C) AS
(SELECT START_T, END_T, BUILDING_NO, COUNT(MEETING_ROOM_ID) FROM
SLICE_MEETINGS
GROUP BY START_T, END_T, BUILDING_NO),
WITH ROOMS_BUILDING (BUILDING_NO, ROOMS_C) AS
(SELECT BUILDING_NO, COUNT(ID) FROM
MEETING_ROOM
GROUP BY BUILDING_NO)
SELECT B.BUILDING_NO, A.START_T, A.END_T
FROM SLICE_COUNT A.
INNER JOIN ROOMS_BUILDING B WHERE A.BUILDING_NO = B.BUILDING_NO AND B.ROOMS_C = A.ROOMS_C;
what it does is (each step corresponds to each CTE definition above)
Get all the time markers, i.e. end or start times
Get all time slices i.e. the smallest unit of time between which there is no other time marker (i.e. no meetings start in a time slice, it's either at the beginning or at the end of a time slice)
Get meetings for each time slice, so now you get something like
10.30 11.00 Room1 BuildingA
10.30 11.00 Room2 BuildingA
11.00 12.00 Room1 BuildingA
Get counts of rooms booked per building per time slice
Filter out timeslice-building combinations that match the number of rooms in each building
Edit
Since mysql doesn't support the WITH clause you'll have to construct views for each (of the 5) WITH clases above. everything else would remain the same.
After reading your comment, I think I understand the problem a bit better. As a first step I would generate a matrix of meeting rooms and time slots using cross join:
select *
from (
select distinct start_date
, end_date
from #meeting_detail
) ts
cross join
#meeting_room mr
Then, for each cell in the matrix, add meetings in that timeslot:
left join
#meeting_detail md
on mr.id = md.meeting_room_id
and ts.start_date < md.end_date
and md.start_date < ts.end_date
And then demand that there are no free rooms. For example, by saying that the left join must succeed for all rooms and time slots. A left join succeeds if any field is not null:
group by
mr.building_no
, ts.start_date
, ts.end_date
having max(case when md.meeting_room_id is null
then 1 else 0 end) = 0
Here's a complete working example. It's written for SQL Server, and the table variables (#meeting_detail) won't work in MySQL. But the report generating query should work in most databases:
set nocount on
declare #meeting_room table (id int, roomname varchar(50),
building_no int)
declare #meeting_detail table (meeting_room_id int,
start_date datetime, end_date datetime)
insert #meeting_room (id, roomname, building_no)
select 1, 'Kitchen', 6
union all select 2, 'Ballroom', 6
union all select 3, 'Conservatory', 7
union all select 4, 'Dining Room', 7
insert #meeting_detail (meeting_room_id, start_date, end_date)
select 1, '2010-08-01 9:00', '2010-08-01 10:00'
union all select 1, '2010-08-01 10:00', '2010-08-01 11:00'
union all select 2, '2010-08-01 10:00', '2010-08-01 11:00'
union all select 3, '2010-08-01 10:00', '2010-08-01 11:00'
select mr.building_no
, ts.start_date
, ts.end_date
from (
select distinct start_date
, end_date
from #meeting_detail
) ts
cross join
#meeting_room mr
left join
#meeting_detail md
on mr.id = md.meeting_room_id
and ts.start_date < md.end_date
and md.start_date < ts.end_date
group by
mr.building_no
, ts.start_date
, ts.end_date
having max(case when md.meeting_room_id is null
then 1 else 0 end) = 0
This prints:
building_no start end
6 2010-08-01 10:00:00.000 2010-08-01 11:00:00.000