Home > Blockchain >  Determine consecutive days within a given window
Determine consecutive days within a given window

Time:10-06

Given the following two tables:

CREATE TABLE #t1 (ID varchar(1), SpecDate date)
CREATE TABLE #t2 (ID varchar(1), Alert varchar(1), AlertDate date)

INSERT INTO #t1 (ID, SpecDate) VALUES ('A', '2021-05-10')
INSERT INTO #t1 (ID, SpecDate) VALUES ('B', '2021-05-10')
INSERT INTO #t1 (ID, SpecDate) VALUES ('B', '2021-05-12')

INSERT INTO #t2 (ID, Alert, AlertDate) VALUES ('A', 'N', '2021-05-06')
INSERT INTO #t2 (ID, Alert, AlertDate) VALUES ('A', 'Y', '2021-05-06')
INSERT INTO #t2 (ID, Alert, AlertDate) VALUES ('A', 'Y', '2021-05-07')
INSERT INTO #t2 (ID, Alert, AlertDate) VALUES ('A', 'Y', '2021-05-08')
INSERT INTO #t2 (ID, Alert, AlertDate) VALUES ('A', 'N', '2021-05-09')
INSERT INTO #t2 (ID, Alert, AlertDate) VALUES ('A', 'N', '2021-05-10')
INSERT INTO #t2 (ID, Alert, AlertDate) VALUES ('A', 'Y', '2021-05-11')
INSERT INTO #t2 (ID, Alert, AlertDate) VALUES ('B', 'N', '2021-05-07')
INSERT INTO #t2 (ID, Alert, AlertDate) VALUES ('B', 'Y', '2021-05-08')
INSERT INTO #t2 (ID, Alert, AlertDate) VALUES ('B', 'N', '2021-05-09')
INSERT INTO #t2 (ID, Alert, AlertDate) VALUES ('B', 'N', '2021-05-10')
INSERT INTO #t2 (ID, Alert, AlertDate) VALUES ('B', 'N', '2021-05-11')
INSERT INTO #t2 (ID, Alert, AlertDate) VALUES ('B', 'N', '2021-05-12')
INSERT INTO #t2 (ID, Alert, AlertDate) VALUES ('B', 'N', '2021-05-13')

#t1:

ID SpecDate
A 2021-05-10
B 2021-05-10
B 2021-05-12

#t2:

ID Alert AlertDate
A N 2021-05-06
A Y 2021-05-06
A Y 2021-05-07
A Y 2021-05-08
A N 2021-05-09
A N 2021-05-10
A Y 2021-05-11
B N 2021-05-07
B Y 2021-05-08
B N 2021-05-09
B N 2021-05-10
B N 2021-05-11
B N 2021-05-12
B N 2021-05-13

I need to find the count of consecutive 'Y' Alerts from #t2 where at least 1 of the 'Y' is within 2 days prior through equal to SpecDate of #t1, ignoring anything after SpecDate and grouped by ID. There can be multiple values of Alert for each ID and AlertDate (if sensor is moved). The final output I am looking for is:

ID SpecDate ConsecutiveAlertDays
A 2021-05-10 3
B 2021-05-10 1

I have gotten to finding the 'Y' within the specified window, but can't figure out how to count consecutive, especially when the count leaves the window:

SELECT DISTINCT t1.ID
    , t1.SpecDate
    , t2.Alert
FROM #t1 AS t1
INNER JOIN #t2 AS t2
    ON t1.ID = t2.ID
WHERE t2.Alert = 'Y' AND
    t2.AlertDate BETWEEN  DATEADD(DAY, -2, t1.SpecDate) AND t1.SpecDate

Edit I was trying to keep the problem as simple and generic as possible, but I think some context might help. I'm working with healthcare data and, for a given day (#t1), I need to know if a patient has had a urinary catheter within the last two days. If yes, then I need to know how long they have had it. #t2 has a daily flag 'Y'/'N' indicating if they had one. There is always at least one daily record, but there can be multiple entries if the patient transfers or has a catheter status changes.

CodePudding user response:

Gaps and islands problem. Sounds like you need to aggregate first if multiple rows per day are possible:

with t2 as (
    select ID, AlertDate, max(Alert) as Alert
    from #t2
    group by ID, AlertDate
), data as (
    select t1.ID, t1.SpecDate,
        row_number() over (partition by t1.ID order by t2.AlertDate) as rn,
        datediff(day, t1.SpecDate, t2.AlertDate) as dn,
        min(t2.AlertDate) over (partition by t1.ID, t1.SpecDate) as firstAlert  
    from #t1 t1 inner join #t2 t2
        on t2.ID = t1.ID
            and t2.Alert = 'Y'
            and t2.AlertDate >= dateadd(day, -2, t1.SpecDate)
), streaks as (
    select ID, SpecDate, count(*) as cnt
    from data
    where firstAlert <= SpecDate
    group by ID, SpecDate, dn - rn
)
select ID, SpecDate, max(cnt) as ConsecutiveAlertDays
from streaks
group by ID, SpecDate;

CodePudding user response:

I think this is what you're after - thank you for the table structure and sample data, but I suspect you'll have other edge cases you didn't include:

;WITH src AS -- group in case more than one 'Y' on a day
(
  SELECT ID, AlertDate
    FROM #t2 
    WHERE Alert = 'Y'
    GROUP BY ID, AlertDate
),
groups AS -- make some islands of consecutive days
(
  SELECT ID, AlertDate, day_group = DATEADD(DAY, 
    1-ROW_NUMBER() OVER (PARTITION BY ID ORDER BY AlertDate), AlertDate)
  FROM src
),
agg AS -- get the count and last day in each island
(
  SELECT ID, AlertDate, 
    c = COUNT(*)       OVER (PARTITION BY ID, day_group),
    m = MAX(AlertDate) OVER (PARTITION BY ID, day_group) 
  FROM groups
)
SELECT t1.ID, t1.SpecDate, ConsecutiveAlertDays = agg.c
  FROM agg
  INNER JOIN #t1 AS t1
    ON agg.m >= DATEADD(DAY, -2, t1.SpecDate)
   AND agg.m <= t1.SpecDate
   AND t1.ID = agg.ID
   AND agg.m = agg.AlertDate
ORDER BY t1.ID, t1.SpecDate; 

Results:

ID SpecDate ConsecutiveAlertDays
A 2021-05-10 3
B 2021-05-10 1
  • Related