У меня есть набор зависимостей, хранящихся в моей базе данных.Я ищу, чтобы найти все объекты, которые зависят от текущего, прямо или косвенно.Поскольку объекты могут зависеть от нуля или более других объектов, совершенно разумно, чтобы объект 1 зависел от объекта 9 дважды (9 зависит от 4 и 5, оба из которых зависят от 1).Я хотел бы получить список всех объектов, которые зависят от текущего объекта без дублирования.
Это становится более сложным, если есть циклы.Без циклов можно использовать DISTINCT, хотя прохождение длинных цепей более одного раза только для их отбраковки в конце все еще является проблемой.Однако с циклами становится важным, чтобы RECURSIVE 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
во внешнем выборе, но это только разумно работает с этими данными, потому что нет цикла.С большим набором данных и большим количеством циклов мы продолжим наращивать вывод CTE только для того, чтобы DISTINCT сократил его обратно.Я бы хотел, чтобы CTE просто остановил эту ветвь, когда уже видел это конкретное значение где-то еще.
Редактировать : речь идет не просто об обнаружении циклов (хотя могут быть циклы).Речь идет о раскрытии всего, на что ссылается этот объект, прямо и косвенно.Так что, если у нас есть 1-> 2-> 3-> 5-> 6-> 7 и 2-> 4-> 5, мы можем начать с 1, перейти к 2, оттуда мы можем перейти к 3 и 4, обаиз этих веток перейдет к 5, но мне не нужны обе ветви - первая может перейти к 5, а другая может просто остановиться на этом.Затем мы переходим к 6 и 7. Большинство обнаружений циклов не находят циклов и возвращают 5, 6, 7 все дважды.Учитывая, что я ожидаю, что большинство моих производственных данных будут иметь 0-3 непосредственных ссылок, и большинство из них будут аналогичными, будет очень распространено, когда будет несколько ветвей от одного объекта к другому, и переход по этим ветвям не будеттолько излишняя, но огромная трата времени и ресурсов.