Обнаружение повторяющихся элементов в рекурсивном CTE

у меня есть набор зависимостей, хранящихся в базе данных. Я ищу, чтобы найти все объекты, которые зависят от текущего, прямо или косвенно. Поскольку объекты могут зависеть от нуля или более других объектов, вполне разумно, что объект 1 зависит от объекта 9 дважды (9 зависит от 4 и 5, оба из которых зависят от 1). Я хочу получить список всех объектов, которые зависят от текущего объекта без дублирования.

это становится более сложным, если есть петли. Без петель можно использовать DISTINCT, хотя пройти через длинные цепи несколько раз только для их отбраковки в конце все еще проблема. Однако с циклами важно, чтобы рекурсивный CTE не объединялся с чем-то, что он уже видел.

Итак, что у меня до сих пор выглядит так:

WITH RECURSIVE __dependents AS (
  SELECT object, array[object.id] AS seen_objects
  FROM immediate_object_dependents(_objectid) object
  UNION ALL
  SELECT object, d.seen_objects || object.id
  FROM __dependents d
  JOIN immediate_object_dependents((d.object).id) object
    ON object.id <> ALL (d.seen_objects)
) SELECT (object).* FROM __dependents;

(это в хранимой процедуре, поэтому я могу передать _objectid)

к сожалению, это просто опускает данный объект, когда я видел его раньше в текущая цепочка, что было бы хорошо, если бы рекурсивный CTE выполнялся сначала по глубине, но когда он широк, это становится проблематичным.

в идеале решение будет в SQL, а не PLPGSQL, но любой из них работает.

в качестве примера я установил это в postgres:

create table objectdependencies (
  id int,
  dependson int
);

create index on objectdependencies (dependson);

insert into objectdependencies values (1, 2), (1, 4), (2, 3), (2, 4), (3, 4);

и затем я попытался запустить это:

with recursive rdeps as (
  select dep
  from objectdependencies dep
  where dep.dependson = 4 -- starting point
  union all
  select dep
  from objectdependencies dep
  join rdeps r
    on (r.dep).id = dep.dependson
) select (dep).id from rdeps;

Я ожидаю" 1, 2, 3 " в качестве выхода.

однако это как-то продолжается вечно (что я также не понимаю). Если я добавлю в level проверить (select dep, 0 as level, ... select dep, level + 1, on ... and level < 3), Я вижу, что 2 и 3 повторяют. И наоборот, если я добавлю увиденную проверку:

with recursive rdeps as (
  select dep, array[id] as seen
  from objectdependencies dep
  where dep.dependson = 4 -- starting point
  union all
  select dep, r.seen || dep.id
  from objectdependencies dep
  join rdeps r
    on (r.dep).id = dep.dependson and dep.id <> ALL (r.seen)
) select (dep).id from rdeps;

затем я получаю 1, 2, 3, 2, 3, и он останавливается. Я мог бы использовать DISTINCT во внешнем select, но это разумно работает только с этими данными, потому что нет цикла. С большим набором данных и большим количеством циклов мы продолжим увеличивать вывод CTE только для того, чтобы вернуть его обратно. Я бы КТР просто остановить эта ветвь, когда она уже видела эту конкретную ценность где-то еще.

редактировать: это не просто обнаружение цикла (хотя могут быть циклы). Речь идет о раскрытии всего, на что ссылается этот объект, прямо и косвенно. Так что если у нас есть 1->2->3->5->6->7 и 2 - >4 - >5, мы можем начать с 1, перейти к 2, оттуда мы можем перейти к 3 и 4, обе эти ветви перейдут к 5, но мне не нужны обе ветви, чтобы сделать это - первая может перейти к 5, а другая может просто остановиться там. Затем переходим к 6 и 7. Большинств обнаружение цикла не найдет никакие циклы и возвратит 5, 6, 7 все дважды. Учитывая, что я ожидаю, что большинство моих производственных данных будут иметь 0-3 непосредственных ссылки, и большинство из них будут аналогичны, будет очень распространено, что будет несколько ветвей от одного объекта к другому, и спускаться по этим ветвям будет не только избыточным, но и огромной тратой времени и ресурсов.

3 ответов


вы можете использовать функцию connectby, которая существует в модуле tablefunc.

сначала вам нужно включить модуль

CREATE EXTENSION tablefunc;

затем вы можете использовать функцию connectby (на основе таблицы образцов, которую вы предоставили в вопросе, это будет следующим образом):

SELECT distinct id
FROM connectby('objectdependencies', 'id', 'dependson', '4', 0)
AS t(id int, dependson int, level int)
where id != 4;

это вернется: Один Два 3

вот объяснение параметров из документации:

connectby(text relname, text keyid_fld, text parent_keyid_fld
          [, text orderby_fld ], text start_with, int max_depth
          [, text branch_delim ])
  • relname имя источника отношение
  • keyid_fld имя ключевого поля
  • parent_keyid_fld имя поля родительского ключа
  • orderby_fld имя поля для заказа братьев и сестер по (необязательно)
  • start_with ключевое значение строки, чтобы начать с
  • max_depth максимальная глубина, чтобы спуститься, или ноль для неограниченной глубины
  • строка branch_delim для разделения Ключей с выходом в ветви (необязательно)

пожалуйста, обратитесь к документация для получения дополнительной информации. https://www.postgresql.org/docs/9.5/static/tablefunc.html


слово dep во втором запросе (после union) является неоднозначным. На самом деле он интерпретируется как столбец rdeps, а не как псевдоним objectdependencies.

with recursive rdeps as (
  select dep
  from objectdependencies dep
  where dep.dependson = 4 -- starting point
  union all
  select dep -- this means r.dep
  from objectdependencies dep
  join rdeps r
    on (r.dep).id = dep.dependson
) select (dep).id from rdeps;

вот почему запрос создает бесконечный цикл. Вы можете исправить это, изменив псевдоним:

with recursive rdeps as (
  select dep
  from objectdependencies dep
  where dep.dependson = 4 -- starting point
  union all
  select objectdep
  from objectdependencies objectdep
  join rdeps r
    on (r.dep).id = objectdep.dependson
) select (dep).id from rdeps;

 id 
----
  1
  2
  3
  1
  2
  1
(6 rows)    

или лучше, просто с помощью столбцов, как Господь намеревался:

with recursive rdeps as (
    select id, dependson
    from objectdependencies
    where dependson = 4
union all
    select d.id, d.dependson
    from objectdependencies d
    join rdeps r
    on r.id = d.dependson
) 
select *
from rdeps;

первый запрос в вопросе-это все, что вы можете сделать в обычном sql, поскольку нет связи между различные ветви (paralel), созданные рекурсивным запросом. В функциональном подходе можно использовать временную таблицу как хранилище, общее для всех ветвей. Функция может выглядеть следующим образом:

create or replace function rec_function(int)
returns void language plpgsql as $$
declare
    i int;
begin
    for i in
        select id
        from objectdependencies
        where dependson = 
    loop
        if not exists(
            select from temp_table 
            where id = i)
        then
            insert into temp_table values(i);
            perform rec_function(i);
        end if;
    end loop;
end $$;

использование:

create temp table temp_table(id int);

select rec_function(4);

select *
from temp_table;

вы можете использовать это для поиска повторяющегося значения

WITH cte AS (
SELECT ROW_NUMBER()OVER(PARTITION BY [FieldName] ORDER BY [FieldName])[Rank],* 
FROM TableName)
SELECT * 
FROM  cte 
WHERE cte.[Rank]>1