Рекуррентный поиск в иерархической таблице PostgreSQL
Таблица хранит в себе поля id_change
, prev_num
, curr_num
и date_change
, где date_change
- дата смены номера, prev_num
- предыдущий номер, curr_num
- текущий номер и id_change
- уникальный идентификатор смены. prev_num
и curr_num
- неуникальные поля. Первый или последний элемент иерархии не выделен.
Первая задача заключается в поиске первого номера и первой даты смены номера к текущему номеру. Пример:
id_change | prev_num | curr_num | date_change |
---|---|---|---|
91 | 0 | 1 | 01.09 |
92 | 1 | 0 | 01.10 |
93 | 0 | 1 | 01.11 |
94 | 1 | 0 | 01.12 |
95 | 0 | 1 | 01.13 |
96 | 1 | 2 | 01.14 |
На выходе должна получится строка вида
start_date | curr_date | curr_num | id_change_arr |
---|---|---|---|
01.09 | 01.14 | 2 | {91,92,93,94,95,96} (порядок неважен) |
Моё решение громоздкое и подразумевает в конце избавление от дубликатов из-за join по дате, который возвращает несколько строк.
with recursive
initial_table_data as (
select id_change, prev_num, curr_num, date_change
from (values
(91, 0, 1, '2024-01-09'::date),
(92, 1, 0, '2024-01-10'::date),
(93, 0, 1, '2024-01-11'::date),
(94, 1, 0, '2024-01-12'::date),
(95, 0, 1, '2024-01-13'::date),
(96, 1, 2, '2024-01-14'::date)
)X(id_change, prev_num, curr_num, date_change)
),
prep_data as (
select date_change as start_date, prev_num as start_num, id_change, prev_num, curr_num, date_change
from (
select id_change, prev_num, curr_num, date_change,
row_number() over (partition by prev_num order by date_change asc) as rn
from initial_table_data
) t1
where rn = 1
),
cte as (
select start_date, start_num, id_change, prev_num, curr_num, date_change, 1 as lvl, array[id_change] as id_change_arr
from prep_data
union all
select cte.start_date, cte.start_num, tbl.id_change, tbl.prev_num, tbl.curr_num, tbl.date_change, cte.lvl + 1 as lvl, tbl.id_change || cte.id_change_arr as id_change_arr
from cte
join initial_table_data tbl
on cte.curr_num = tbl.prev_num and cte.date_change < tbl.date_change --проблемное условие
)
select start_date, date_change, curr_num, id_change_arr
from (
select *,
row_number() over (partition by start_num order by lvl desc) as rn_1,
row_number() over (partition by curr_num order by lvl desc) as rn_2
from cte
) t1
where rn_1 = 1 and rn_2 = 1 --избавление от дубликатов
В запросе ищутся первые номера по полю date_change
и затем на них рекурсивным join добавляются данные по следующей иерархии. Этот запрос возвращает верное решение, однако он долгий из-за порождения дублей и последующего избавления от них. Можно ли в рекурсивном cte ограничить этот join убрав дубли? ORDER BY
+ LIMIT 1
не работает.