Query to compare dates patients are missing from hospital census - sql

I have a hospital bed census that is triggered and creates a date/time stamped row in a table. when the bed check portion is done it labels the event census. i have found that some patients on days they were in the hospital have not been timestamped with the event census. I am trying to write a query to capture all patients that may have had this issue.
i need to capture the patients between their admit and discharge dates, and then any day they do not have a time stamp event of census. for example, this patient does not have a census on the 12th or 13th but does on the 14th. i want to be able to pull this pat_id and dates they are not stamped with census.
11-APR-2019 11:59:00 PM CENSUS
12-APR-2019 03:12:00 PM TRANSFER OUT
12-APR-2019 03:12:00 PM TRANSFER IN
14-APR-2019 07:06:00 AM PATIENT UPDATE
14-APR-2019 11:40:00 AM TRANSFER OUT
14-APR-2019 11:40:00 AM TRANSFER IN
14-APR-2019 11:59:00 PM CENSUS
I created a calendar portion to my query. then i created a query to capture patients in a time frame. from there i am a bit stuck.
DATE1
AS
(select
to_char(dates,'MM/DD/YYYY') AS WEEK_DATE,
dates,
to_char(dates,'D') weekday,
to_char(dates,'mm') m_onth,
to_char(dates,'ww') week_of_year,
to_char(dates,'dd') month_day,
to_char(dates,'ddd') Year_day,
SUBSTR(dates,1,2) AS WEEKDATE
from (SELECT TRUNC(to_date(v.yyyy,'YYYY'),'YY') +LEVEL - 1 DATES
FROM ( SELECT 2019 yyyy FROM dual ) v
CONNECT BY LEVEL < 366
)
)
,
ADT
AS (select distinct
adt.pat_id,
peh.y_mrn,
adt.DEPARTMENT_ID,
adp.department_name,
--peh.HOSP_ADMSN_TIME,
to_char(peh.HOSP_ADMSN_TIME,'MM/DD/YYYY') AS HOSP_ADMSN_TIME2,
--peh.HOSP_DISCH_TIME,
to_char(peh.HOSP_DISCH_TIME,'MM/DD/YYYY') AS HOSP_DISCH_TIME2,
adt.effective_time,
to_char(aDT.effective_time,'MM/DD/YYYY') AS EFFECT_DATE,
--LEAD(adt.effective_time) over (partition by ADT.pat_id order by ADT.pat_id, adt.effective_time) AS NEXT_EFF_DATE,
--CASE WHEN adt.event_type_c =6 THEN adt.effective_time END AS CENSUS_DATE,
et.title as event_type,
adt.event_type_c,
peh.ADT_PAT_CLASS_C,
Adt.event_subtype_c--,
--LAG(adt.effective_time) over (partition by ADT.pat_id order by ADT.pat_id, adt.effective_time) AS PREV_EFF_DATE
from
clarity_adt adt
left OUTER join
pat_enc_hsp peh
on
peh.pat_enc_csn_id = adt.pat_enc_csn_id
left outer join
clarity_dep adp
on adt.department_id = adp.department_id
left OUTER join
zc_event_type et
on adt.event_type_c = et.event_type_c
where
adt.effective_time between '08-apr-2019' and '15-apr-2019'
order by adt.effective_time
)
,
ADT2
AS
(
SELECT-- DISTINCT
D.WEEK_DATE,
A.HOSP_ADMSN_TIME2,
A.EFFECT_DATE,
A.PAT_ID,
CASE WHEN D.WEEK_DATE IS NOT NULL AND A.EFFECT_DATE IS NULL AND A.event_type <> 'CENSUS' THEN 1
WHEN D.WEEK_DATE IS NOT NULL AND A.EFFECT_DATE IS NULL AND A.event_type IS NULL THEN 1
WHEN D.WEEK_DATE IS NOT NULL AND A.EFFECT_DATE IS NOT NULL AND A.event_type <> 'CENSUS' THEN 1 ELSE 0
END AS NO_ADT_INFO,
A.event_type,
A.HOSP_DISCH_TIME2
FROM
DATE2 D
LEFT OUTER JOIN
ADT A
ON
D.WEEK_DATE = A.EFFECT_DATE
ORDER BY
D.WEEK_DATE)
i would like to end up with the patient id, the day of the week they have no census, the hosp admission & discharge dates
PAT_ID WEEK_DATE EVENT_TYPE HOSP_ADMSN_TIME HOSP_DISCH_TIME
ABCDEF 4/12/2019 NO CENSUS 4/10/2019 4/19/2019
ABCDEF 4/13/2019 NO CENSUS 4/10/2019 4/19/2019
GHIJK 4/8/2019 NO CENSUS 4/2/2019 4/12/2019
GHIJK 4/11/2019 NO CENSUS 4/2/2019 4/12/2019

Here is sample data for two patients:
events(pat_id, event_date, event_type) as (
select 'ABCD', to_date('2019-04-11 23:59', 'yyyy-mm-dd hh24:mi'), 'CENSUS' from dual union all
select 'ABCD', to_date('2019-04-12 15:12', 'yyyy-mm-dd hh24:mi'), 'TRANSFER OUT' from dual union all
select 'ABCD', to_date('2019-04-12 15:12', 'yyyy-mm-dd hh24:mi'), 'TRANSFER IN' from dual union all
select 'ABCD', to_date('2019-04-14 07:06', 'yyyy-mm-dd hh24:mi'), 'PATIENT UPDATE' from dual union all
select 'ABCD', to_date('2019-04-14 11:40', 'yyyy-mm-dd hh24:mi'), 'TRANSFER OUT' from dual union all
select 'ABCD', to_date('2019-04-14 11:40', 'yyyy-mm-dd hh24:mi'), 'TRANSFER IN' from dual union all
select 'ABCD', to_date('2019-04-14 23:59', 'yyyy-mm-dd hh24:mi'), 'CENSUS' from dual union all
select 'GHIJ', to_date('2019-05-17 23:59', 'yyyy-mm-dd hh24:mi'), 'CENSUS' from dual union all
select 'GHIJ', to_date('2019-05-19 23:59', 'yyyy-mm-dd hh24:mi'), 'CENSUS' from dual ),
peh(pat_id, hosp_admsn_time, hosp_disch_time) as (
select 'ABCD', date '2019-04-11', date '2019-04-14' from dual union all
select 'GHIJ', date '2019-05-17', date '2019-05-20' from dual ),
You can create recursive query generating days for each patient and check if there is CENSUS event for each of these days:
with cte(pat_id, num, adm, dis) as (
select pat_id, 0, hosp_admsn_time, hosp_disch_time from peh
union all
select pat_id, num + 1, adm, dis from cte where num < dis - adm)
select pat_id, day, 'NO CENSUS' info, adm, dis
from (select pat_id, adm + num day, adm, dis from cte) d
where not exists (
select 1
from events
where pat_id = d.pat_id and trunc(event_date) = d.day and event_type = 'CENSUS')
order by pat_id, day;
Result:
PAT_ID DAY INFO ADM DIS
------ ----------- --------- ----------- -----------
ABCD 2019-04-12 NO CENSUS 2019-04-11 2019-04-14
ABCD 2019-04-13 NO CENSUS 2019-04-11 2019-04-14
GHIJ 2019-05-18 NO CENSUS 2019-05-17 2019-05-20
GHIJ 2019-05-20 NO CENSUS 2019-05-17 2019-05-20
dbfiddle demo

Related

SQL: How to split data from quaterly to monthly with date

I have the data in the sql table in quarterly format. I need to be able to split it into monthly with value split evenly ([value/3) in to each month. Can you please assist on how to achieve this using SQL? Thank you.
start
end
value
2022-01-01
2022-04-01
25629
2022-04-01
2022-07-01
993621
CREATE TABLE #your_tbl
("start_dt" timestamp, "end_dt" timestamp, "values" int)
;
INSERT INTO #your_tbl
("start_dt", "end_dt", "values")
VALUES
('2020-01-01 00:00:00', '2020-04-01 00:00:00', 114625),
('2020-04-01 00:00:00', '2020-07-01 00:00:00', 45216),
('2020-07-01 00:00:00', '2020-10-01 00:00:00', 513574)
DECLARE #datefrom datetime
DECLARE #dateto datetime
SET #datefrom='2022-04-01'
SET #dateto = '2022-07-01'
;WITH cte AS
(
SELECT #datefrom as MyDate
UNION ALL
SELECT DATEADD(month,1,MyDate)
FROM cte
WHERE DATEADD(month,1,MyDate)<#dateto
),
combined AS (
SELECT *
FROM #your_tbl q
JOIN cte m
ON YEAR(m.MyDate) >= q.start_dt
AND MONTH(m.MyDate) < q.end_dt
)
SELECT *, [values]/COUNT(1) OVER(PARTITION BY [start_dt], [end_dt]) as monthly_values
FROM combined
DROP TABLE #your_tbl
In Oracle can you use this script:
with mytable as (
select to_date('2022-01-01', 'YYYY-MM-DD') as startX, to_date('2022-04-01', 'YYYY-MM-DD') as endX, 25629 as valueX from dual union
select to_date('2022-04-01', 'YYYY-MM-DD') as startX, to_date('2022-07-01', 'YYYY-MM-DD') as endX, 993621 as valueX from dual union
select to_date('2022-07-01', 'YYYY-MM-DD') as startX, to_date('2022-10-01', 'YYYY-MM-DD') as endX, 21 as valueX from dual union
select to_date('2022-10-01', 'YYYY-MM-DD') as startX, to_date('2023-01-01', 'YYYY-MM-DD') as endX, 7777 as valueX from dual
),
mymonths as (
select '01' as month_n from dual union
select '02' as month_n from dual union
select '03' as month_n from dual union
select '04' as month_n from dual union
select '05' as month_n from dual union
select '06' as month_n from dual union
select '07' as month_n from dual union
select '08' as month_n from dual union
select '09' as month_n from dual union
select '10' as month_n from dual union
select '11' as month_n from dual union
select '12' as month_n from dual
)
select month_n, startX, valueX/3
from mytable, mymonths
where month_n between to_char(startX, 'MM') and to_char(endX-1, 'MM');
MONTHS_N STARTX VALUEX/3
-------- ---------- ----------
01 01/01/2022 8543
02 01/01/2022 8543
03 01/01/2022 8543
04 01/04/2022 331207
05 01/04/2022 331207
06 01/04/2022 331207
07 01/07/2022 7
08 01/07/2022 7
09 01/07/2022 7
10 01/10/2022 2592,33333
11 01/10/2022 2592,33333
12 01/10/2022 2592,33333
Thank you.
Assuming you can figure out how to generate monthly dates, which is RDBMS dependent, here's a solution that might work depending on if you can use window functions.
Note this doesn't hard-code divide by 3 in case you're in a partial quarter.
WITH combined AS (
SELECT *,
FROM your_tbl q
JOIN monthly_dates m
ON m.monthly_dt >= q.start_dt
AND m.monthly_dt < q.end_dt
)
SELECT *
, values / COUNT(1) OVER(PARTITION BY start_dt, end_dt) as monthly_values
FROM combined
sqlfiddle

Oracle: getting an average by week for the timespan of available data

I have some data that shows daily logins by clients on every available date they logged in that streches back a few years.
date month clientId loginCount
------------ --------- ---------- ------------
01/01/2021 01-2021 1234 234
02/01/2021 01-2021 1234 978
01/02/2021 02-2021 6547 45
01/02/2021 02-2021 345 86
....
For each client, I would like to generate the average number of times they login every week for however long they have corresponding date entries in the table :
clientId avgWeeklyLoginCount
---------- ---------------------
1234 125
6547 26
345 48
I understand 'IW' could be used in the TO_CHAR function to do this, e.g.
SELECT
TO_CHAR(date,'IW'),
clientId,
SUM(loginCount) as summedCount
FROM
logins
GROUP BY
TO_CHAR(date,'IW')
but not sure how to get an average by client id from this. any help will be appreciated!
You can using it as example. It can be looks like unnecessary overcomplicated:
ceil((in_date - trunc(to_date('06.01.0001', 'dd.MM.yyyy'), 'IW'))/7)
It means number of week since 1 CE. If your dates contain within single year you can use TO_CHAR(date,'IW') or TO_CHAR(date,'WW') instead of.
with logins(in_date, clientId, loginCount) as (
select to_date('01/01/2021 01:00:00', 'dd/MM/yyyy HH:MI:SS'), 1234, 234 from dual union all
select to_date('02/01/2021 01:00:00', 'dd/MM/yyyy HH:MI:SS'), 1234, 978 from dual union all
select to_date('01/02/2021 01:00:00', 'dd/MM/yyyy HH:MI:SS'), 6547, 45 from dual union all
select to_date('01/02/2021 01:00:00', 'dd/MM/yyyy HH:MI:SS'), 345, 86 from dual union all
select to_date('31/12/2020 01:00:00', 'dd/MM/yyyy HH:MI:SS'), 347, 1 from dual union all
select to_date('01/01/2021 01:00:00', 'dd/MM/yyyy HH:MI:SS'), 347, 1 from dual
)
select
clientId, avg(loginCount) avgLoginCountPerWeek
from (
select
week_number, clientId, sum(loginCount) loginCountPerWeek
from (
select
ceil((in_date - trunc(to_date('06.01.0001', 'dd.MM.yyyy'), 'IW'))/7) week_number, clientId, loginCount
from
logins
) t
group by
week_number, clientId
)
group by
clientId
You can use an aggregation query and count(distinct):
select clientid,
count(*) / count(distinct trunc(in_date, 'WW')) as avg_per_week
from logins
group by clientid;

How can I get the number of working days by comparing two tables?

I currently have 3 tables, a table storing task details, a table storing staff details and the task assignment as below.
TASKS
TASK_ID
TASK_START_DATE
TASK_END_DATE
001
03-04-2021
05-04-2021
002
05-04-2021
07-04-2021
STAFFS
STAFF_ID
STAFF_WORK_DAY
1001
MONDAY, WEDNESDAY, FRIDAY
1002
MONDAY, TUESDAY
TASK_ASSIGNMENT
STAFF_ID
TASK_ID
1001
001
1002
002
I extracted the dates for each tasks and converted them into week day names using TO_CHAR
TASK_ID
DATE
001
SATURDAY
001
SUNDAY
001
MONDAY
002
MODNDAY
002
TUESDAY
002
WEDNESDAY
How can I compare the tables and find out the number of working days for each task in its duration?
Expected result
TASK_ID
STAFF_ID
COUNT(WORK_DAY)
001
1001
1
002
1002
2
You can use:
WITH days ( id, day ) AS (
SELECT 0, 'MONDAY' FROM DUAL UNION ALL
SELECT 1, 'TUESDAY' FROM DUAL UNION ALL
SELECT 2, 'WEDNESDAY' FROM DUAL UNION ALL
SELECT 3, 'THURSDAY' FROM DUAL UNION ALL
SELECT 4, 'FRIDAY' FROM DUAL UNION ALL
SELECT 5, 'SATURDAY' FROM DUAL UNION ALL
SELECT 6, 'SUNDAY' FROM DUAL
)
SELECT t.task_id,
s.staff_id,
COUNT(*)
FROM staff s
INNER JOIN days d
ON ( s.staff_work_day LIKE '%' || d.day || '%' )
INNER JOIN task_assignment ta
ON ( ta.staff_id = s.staff_id )
INNER JOIN tasks t
ON ( ta.task_id = t.task_id )
INNER JOIN LATERAL (
SELECT t.TASK_START_DATE + LEVEL - 1 AS task_day
FROM DUAL
CONNECT BY LEVEL <= t.TASK_END_DATE - t.TASK_START_DATE + 1
) td
ON ( td.task_day - TRUNC( td.task_day, 'IW' ) = d.id )
GROUP BY t.task_id, s.staff_id
Which, for the sample data:
CREATE TABLE tasks ( TASK_ID, TASK_START_DATE, TASK_END_DATE ) AS
SELECT '001', DATE '2021-04-03', DATE '2021-04-05' FROM DUAL UNION ALL
SELECT '002', DATE '2021-04-05', DATE '2021-04-07' FROM DUAL;
CREATE TABLE STAFF( STAFF_ID, STAFF_WORK_DAY ) AS
SELECT 1001, 'MONDAY, WEDNESDAY, FRIDAY' FROM DUAL UNION ALL
SELECT 1002, 'MONDAY, TUESDAY' FROM DUAL;
CREATE TABLE TASK_ASSIGNMENT( STAFF_ID, TASK_ID ) AS
SELECT 1001, '001' FROM DUAL UNION ALL
SELECT 1002, '002' FROM DUAL;
Outputs:
TASK_ID
STAFF_ID
COUNT(*)
002
1002
2
001
1001
1
db<>fiddle here

Month counts between dates

I have the below table. I need to count how many ids were active in a given month. So thinking I'll need to create a row for each id that was active during that month so that id can be counted each month. A row should be generated for a term_dt during that month.
active_dt term_dt id
1/1/2018 101
1/1/2018 5/15/2018 102
3/1/2018 6/1/2018 103
1/1/2018 4/25/18 104
Apparently this is a "count number of overlapping intervals" problem. The algorithm goes like this:
Create a sorted list of all start and end points
Calculate a running sum over this list, add one when you encounter a start and subtract one when you encounter an end
If two points are same then perform subtractions first
You will end up with list of all points where the sum changed
Here is a rough outline of the query. It is for SQL Server but could be ported to any RDBMS that supports window functions:
WITH cte1(date, val) AS (
SELECT active_dt, 1 FROM #t AS t
UNION ALL
SELECT COALESCE(term_dt, '2099-01-01'), -1 FROM #t AS t
-- if end date is null then assume the row is valid indefinitely
), cte2 AS (
SELECT date, SUM(val) OVER(ORDER BY date, val) AS rs
FROM cte1
)
SELECT YEAR(date) AS YY, MONTH(date) AS MM, MAX(rs) AS MaxActiveThisYearMonth
FROM cte2
GROUP BY YEAR(date), MONTH(date)
DB Fiddle
I was toying with a simpler query, that seemed to do the trick, for Oracle:
with candidates (month_start) as (
select to_date ('2018-' || column_value || '-01','YYYY-MM-DD')
from
table
(sys.odcivarchar2list('01','02','03','04','05',
'06','07','08','09','10','11','12'))
), sample_data (active_dt, term_dt, id) as (
select to_date('01/01/2018', 'MM/DD/YYYY'), null, 101 from dual
union select to_date('01/01/2018', 'MM/DD/YYYY'),
to_date('05/15/2018', 'MM/DD/YYYY'), 102 from dual
union select to_date('03/01/2018', 'MM/DD/YYYY'),
to_date('06/01/2018', 'MM/DD/YYYY'), 103 from dual
union select to_date('01/01/2018', 'MM/DD/YYYY'),
to_date('04/25/2018', 'MM/DD/YYYY'), 104 from dual
)
select c.month_start, count(1)
from candidates c
join sample_data d
on c.month_start between d.active_dt and nvl(d.term_dt,current_date)
group by c.month_start
order by c.month_start
An alternative solution would be to use a hierarchical query, e.g.:
WITH your_table AS (SELECT to_date('01/01/2018', 'dd/mm/yyyy') active_dt, NULL term_dt, 101 ID FROM dual UNION ALL
SELECT to_date('01/01/2018', 'dd/mm/yyyy') active_dt, to_date('15/05/2018', 'dd/mm/yyyy') term_dt, 102 ID FROM dual UNION ALL
SELECT to_date('01/03/2018', 'dd/mm/yyyy') active_dt, to_date('01/06/2018', 'dd/mm/yyyy') term_dt, 103 ID FROM dual UNION ALL
SELECT to_date('01/01/2018', 'dd/mm/yyyy') active_dt, to_date('25/04/2018', 'dd/mm/yyyy') term_dt, 104 ID FROM dual)
SELECT active_month,
COUNT(*) num_active_ids
FROM (SELECT add_months(TRUNC(active_dt, 'mm'), -1 + LEVEL) active_month,
ID
FROM your_table
CONNECT BY PRIOR ID = ID
AND PRIOR sys_guid() IS NOT NULL
AND LEVEL <= FLOOR(months_between(coalesce(term_dt, SYSDATE), active_dt)) + 1)
GROUP BY active_month
ORDER BY active_month;
ACTIVE_MONTH NUM_ACTIVE_IDS
------------ --------------
01/01/2018 3
01/02/2018 3
01/03/2018 4
01/04/2018 4
01/05/2018 3
01/06/2018 2
01/07/2018 1
01/08/2018 1
01/09/2018 1
01/10/2018 1
Whether this is more or less performant than the other answers is up to you to test.

SQL , Analytical Functions , rownumber

I need to get same rownumber or numeric value in SQL to group values that match conditions like the following example:
If we have same Agent name and the time variance between current row and preceding row value is less than 06:00 hours after applying partition by name and ordering by time
then add same rownumber else increase it.
example for row data and output of rownumber:
person date_time rownumber
A 01/04/2018 10:00 1
A 01/04/2018 13:00 1
A 01/04/2018 14:00 1
A 01/04/2018 15:00 1
A 01/04/2018 23:00 2
A 02/04/2018 03:00 2
A 02/04/2018 12:00 3
A 02/04/2018 16:00 3
B 01/04/2018 17:00 4
B 01/04/2018 20:30 4
C 01/04/2018 18:00 5
C 01/04/2018 22:00 5
You can do this with a combination of LAG and SUM analytic functions, like so:
WITH your_table AS (SELECT 'A' person, to_date('01/04/2018 10', 'dd/mm/yyyy hh24') date_time FROM dual UNION ALL
SELECT 'A' person, to_date('01/04/2018 13', 'dd/mm/yyyy hh24') date_time FROM dual UNION ALL
SELECT 'A' person, to_date('01/04/2018 14', 'dd/mm/yyyy hh24') date_time FROM dual UNION ALL
SELECT 'A' person, to_date('01/04/2018 15', 'dd/mm/yyyy hh24') date_time FROM dual UNION ALL
SELECT 'A' person, to_date('01/04/2018 23', 'dd/mm/yyyy hh24') date_time FROM dual UNION ALL
SELECT 'A' person, to_date('02/04/2018 03', 'dd/mm/yyyy hh24') date_time FROM dual UNION ALL
SELECT 'A' person, to_date('02/04/2018 12', 'dd/mm/yyyy hh24') date_time FROM dual UNION ALL
SELECT 'A' person, to_date('02/04/2018 16', 'dd/mm/yyyy hh24') date_time FROM dual UNION ALL
SELECT 'B' person, to_date('01/04/2018 17', 'dd/mm/yyyy hh24') date_time FROM dual UNION ALL
SELECT 'B' person, to_date('01/04/2018 20', 'dd/mm/yyyy hh24') date_time FROM dual UNION ALL
SELECT 'C' person, to_date('01/04/2018 18', 'dd/mm/yyyy hh24') date_time FROM dual UNION ALL
SELECT 'C' person, to_date('01/04/2018 22', 'dd/mm/yyyy hh24') date_time FROM dual)
SELECT person,
date_time,
SUM(period_change) OVER (ORDER BY person, date_time) rownumber
FROM (SELECT person,
date_time,
CASE WHEN date_time - LAG(date_time, 1, date_time - 7/24) OVER (PARTITION BY person ORDER BY date_time) > 6/24 THEN 1 ELSE 0 END period_change
FROM your_table);
PERSON DATE_TIME ROWNUMBER
------ ----------- ----------
A 01/04/2018 1
A 01/04/2018 1
A 01/04/2018 1
A 01/04/2018 1
A 01/04/2018 2
A 02/04/2018 2
A 02/04/2018 3
A 02/04/2018 3
B 01/04/2018 4
B 01/04/2018 4
C 01/04/2018 5
C 01/04/2018 5
This works by putting 1 in the additional column whenever a new group is triggered.
Once you have that, then you can do a running sum on that column. That means that after every group change, subsequent rows will be assigned the same number, up until the next group change.
N.B. As suggested by Peter Lang in the comments below, you might prefer to change the case statement generating the "period_change" column to:
CASE WHEN date_time - LAG(date_time) OVER (PARTITION BY person ORDER BY date_time) < 6/24 THEN 0 ELSE 1 END