SELECT *
FROM (SELECT date'2021-05-06' AS tran_dt FROM dual UNION
SELECT date'2021-05-24' FROM dual UNION
SELECT date'2021-06-25' FROM dual UNION
SELECT date'2021-07-02' FROM dual UNION
SELECT date'2021-07-27' FROM dual UNION
SELECT date'2021-08-16' FROM dual UNION
SELECT date'2021-08-23' FROM dual UNION
SELECT date'2021-10-01' FROM dual UNION
SELECT date'2021-12-31' FROM dual)
Hello
I am working with a date field in SQL Oracle.
I am trying to create rolling window that returns the first row and returns another row when the date is >90 since the last returned row.
In the below example, I want to calculate and return where the Alert field = Yes.
Grateful if anyone has any ideas.
Thanks
Shee7
TRAN_DT Alert Days Since Last Alert
06/05/2021 Yes 0
24/05/2021 No 18
25/06/2021 No 50
02/07/2021 No 57
27/07/2021 No 82
16/08/2021 Yes 102
23/08/2021 No 7
01/10/2021 No 46
31/12/2021 Yes 137
CodePudding user response:
You can use a recursion
with firstr as (
-- a row to start with
select TRAN_DT
from (
select t.*
, row_number() over(order by TRAN_DT) rn
from trans t
)
where rn=1
),
cte(TRAN_DT) as (
select *
from firstr
union all
select t.*
from cte c
join trans t on t.TRAN_DT >= c.TRAN_DT 90 and not exists (
select 1
from trans t2
where t2.TRAN_DT >= c.TRAN_DT 90 and t2.TRAN_DT < t.TRAN_DT)
)
select *
from cte;
CodePudding user response:
Maybe there will be a third answer
with tab1 as (
SELECT TRAN_DT
FROM (SELECT TO_DATE('20210506','YYYYMMDD') TRAN_DT FROM DUAL UNION
SELECT TO_DATE('20210524','YYYYMMDD') TRAN_DT FROM DUAL UNION
SELECT TO_DATE('20210625','YYYYMMDD') TRAN_DT FROM DUAL UNION
SELECT TO_DATE('20210702','YYYYMMDD') TRAN_DT FROM DUAL UNION
SELECT TO_DATE('20210727','YYYYMMDD') TRAN_DT FROM DUAL UNION
SELECT TO_DATE('20210816','YYYYMMDD') TRAN_DT FROM DUAL UNION
SELECT TO_DATE('20210823','YYYYMMDD') TRAN_DT FROM DUAL UNION
SELECT TO_DATE('20211001','YYYYMMDD') TRAN_DT FROM DUAL UNION
SELECT TO_DATE('20211231','YYYYMMDD') TRAN_DT FROM DUAL)
)
, tab2 as (
select t1.tran_dt dt,
row_number() over(order by t1.tran_dt) rn
from tab1 t1
)
, t0(dt, rn, temp) as (
select t1.dt, rn, t1.dt from tab2 t1 where t1.rn = 1
union all
select t2.dt,
t2.rn,
case when t2.dt - t0.temp > 90 then t2.dt else t0.temp end
from tab2 t2, t0
where t0.rn 1 = t2.rn
)
select t1.dt,
case when t1.dt = t1.temp then 'yes' else 'no' end,
nvl(t1.dt - lag(t1.temp) over(order by t1.rn), 0)
from t0 t1
The third answer, maybe there will be a fourth one
with tab1 as (
SELECT TRAN_DT
FROM (SELECT TO_DATE('20210506','YYYYMMDD') TRAN_DT FROM DUAL UNION
SELECT TO_DATE('20210524','YYYYMMDD') TRAN_DT FROM DUAL UNION
SELECT TO_DATE('20210625','YYYYMMDD') TRAN_DT FROM DUAL UNION
SELECT TO_DATE('20210702','YYYYMMDD') TRAN_DT FROM DUAL UNION
SELECT TO_DATE('20210727','YYYYMMDD') TRAN_DT FROM DUAL UNION
SELECT TO_DATE('20210816','YYYYMMDD') TRAN_DT FROM DUAL UNION
SELECT TO_DATE('20210823','YYYYMMDD') TRAN_DT FROM DUAL UNION
SELECT TO_DATE('20211001','YYYYMMDD') TRAN_DT FROM DUAL UNION
SELECT TO_DATE('20211231','YYYYMMDD') TRAN_DT FROM DUAL)
)
, tab2 as (
select t1.tran_dt dt
from tab1 t1
order by t1.tran_dt
fetch next 1 row only
)
, t0(dt) as (
select t1.dt from tab2 t1
union all
select (select min(v1.TRAN_DT) from tab1 v1 where t1.dt 90 < v1.TRAN_DT)
from t0 t1
where t1.dt is not null
)
select t1.dt
from t0 t1
where t1.dt is not null
CodePudding user response:
From Oracle 12, this is the sort of query that MATCH_RECOGNIZE
is intended for:
SELECT tran_dt,
alert,
tran_dt
- LAG(CASE alert WHEN 'Yes' THEN tran_dt END, 1, tran_dt)
IGNORE NULLS OVER (ORDER BY tran_dt)
AS days
FROM table_name
MATCH_RECOGNIZE (
ORDER BY tran_dt
MEASURES
CLASSIFIER() AS alert
ALL ROWS PER MATCH
PATTERN ( "Yes" "No"* )
DEFINE
"No" AS tran_dt <= "Yes".tran_dt INTERVAL '90' DAY
)
Before Oracle 12, you can use a recursive query:
WITH dates (tran_dt, rn) AS (
SELECT tran_dt,
ROW_NUMBER() OVER (ORDER BY tran_dt) AS rn
FROM table_name
),
rolling_dates (tran_dt, alert, rn, days, last_alert) AS (
SELECT tran_dt, 'Yes', rn, 0, tran_dt
FROM dates
WHERE rn = 1
UNION ALL
SELECT d.tran_dt,
CASE
WHEN d.tran_dt <= r.last_alert INTERVAL '90' DAY
THEN 'No'
ELSE 'Yes'
END,
d.rn,
d.tran_dt - r.last_alert,
CASE
WHEN d.tran_dt <= r.last_alert INTERVAL '90' DAY
THEN r.last_alert
ELSE d.tran_dt
END
FROM rolling_dates r
INNER JOIN dates d
ON (r.rn 1 = d.rn)
)
SELECT tran_dt,
alert,
days
FROM rolling_dates;
Both of which outputs:
TRAN_DT ALERT DAYS 2021-05-06 00:00:00 Yes 0 2021-05-24 00:00:00 No 18 2021-06-25 00:00:00 No 50 2021-07-02 00:00:00 No 57 2021-07-27 00:00:00 No 82 2021-08-16 00:00:00 Yes 102 2021-08-23 00:00:00 No 7 2021-10-01 00:00:00 No 46 2021-12-31 00:00:00 Yes 137
db<>fiddle here