I have the table like:
start_dt | end_dt | ID | Index |
---|---|---|---|
2023-01-01 | 2023-03-08 | A | i |
2023-05-08 | 2023-06-18 | A | i+1 |
2023-12-09 | 2024-02-02 | A | i+2 |
2024-12-01 | 2025-01-05 | A | i+3 |
2023-02-01 | 2023-03-07 | B | j |
2023-06-08 | 2023-07-18 | B | j+1 |
2024-12-09 | 2024-02-02 | B | j+2 |
I need to create new one which satisfies the conditions (for each ID separately):
if start_dt[i+1] – end_dt[i] < 9 months, then the rows i and i+1 collapse, start_dt_new = start_dt[i], end_dt_new = end_dt[i+1].
so the resulted table will look like
start_dt | end_dt | ID |
---|---|---|
2023-01-01 | 2024-02-02 | A |
2024-12-01 | 2025-01-05 | A |
2023-02-01 | 2023-07-18 | B |
2024-12-09 | 2024-02-02 | B |
how can I do it with PostgreSQL?
What I tried:
create table test (
start_dt date,
end_dt date,
id varchar(10)
);
insert into test(start_dt, end_dt, id)
values (date'2023-01-01', date'2023-03-08', 'A'),
(date'2023-05-08', date'2023-06-18', 'A'),
(date'2023-12-09', date'2024-02-02', 'A'),
(date'2024-12-01', date'2025-01-05', 'A'),
(date'2023-02-01', date'2023-03-07', 'B'),
(date'2023-06-08', date'2023-07-18', 'B'),
(date'2024-12-09', date'2024-02-02', 'B');
with a as (
select a.*,
case when age(lead(start_dt) over(partition by id order by start_dt), end_dt)
< interval'9 months' then 1 else 0 end monotony_f
from test a
order by id, start_dt
),
b as (
select start_dt, end_dt, id, coalesce(lag(monotony_f) over(partition by id order by start_dt), 1) monotony_f
from a
order by id, start_dt
)
select start_dt, end_dt, id from b
where monotony_f = 0
union
select min(start_dt) start_dt, max(end_dt) end_dt, id from b
where monotony_f = 1
group by id
order by id, start_dt
But may be there is more correct and natural way? my solution does not cover such example:
start_dt | end_dt | ID | Index |
---|---|---|---|
2023-01-01 | 2023-03-08 | A | i |
2023-05-08 | 2023-06-18 | A | i+1 |
2023-12-09 | 2024-02-02 | A | i+2 |
2024-12-01 | 2025-01-05 | A | i+3 |
2025-11-01 | 2025-12-07 | A | i+4 |
2026-01-08 | 2026-07-18 | A | i+5 |
2
Answers
Example
before grouping
with recursive x as (
select
sk,
id,
start_dt ,
end_dt,
sk || ” sk_path
from
test
–where sk=1
union all
select
t2.sk sk,
t2.id,
x.start_dt ,
t2.end_dt,
x.sk_path || t2.sk
from
test t2
join x on
x.id = t2.id
and x.sk + 1 = t2.sk
and age(t2.start_dt ,
x.end_dt) < interval ‘9 months’
select
sk,
id,
start_dt ,
end_dt,
sk_path
from
x
where
not exists (
select
1
from
x x2
where
x.id = x2.id
and (( x.start_dt > x2.start_dt
and x.start_dt < x2.end_dt )
or
( x.end_dt > x2.start_dt
and x.end_dt < x2.end_dt ) ))