Рекуррентный поиск в иерархической таблице 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 не работает.


Ответы (0 шт):