检测递归CTE中的重复项

时间:2018-05-22 23:47:40

标签: sql postgresql common-table-expression

我的数据库中存储了一组依赖项。我希望找到所有依赖于当前对象的对象,无论是直接还是间接。由于对象可以依赖于零个或多个其他对象,因此对象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正在深度优先完成,那么这将是正常的,但是当它是广度优先时,它会成为问题。 / p>

理想情况下,解决方案将使用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 + 1on ... 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个直接引用,而且大多数都是同样的,从一个对象到另一个对象的多个分支将是非常常见的,而且那些分支将不会只是多余但却浪费大量时间和资源。

4 个答案:

答案 0 :(得分:6)

第二个查询中的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中做的所有事情,因为递归查询生成的不同(并行)分支之间没有通信。在功能方法中,您可以使用临时表作为所有分支共用的存储。该功能可能如下所示:

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 = $1
    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;

答案 1 :(得分:2)

您可以使用tablefunc模块中存在的connectby函数。

首先,您需要启用模块

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;

这将返回: 1 2 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

答案 2 :(得分:2)

我知道这不是一个很老的问题,但是我很惊讶没有人建议从all中删除union以及早消除重复项。这是一种防止递归CTE结果重复的相当简单的方法,但它确实有一些警告-这种结果必须仅包含实数字段,即,不包含depthpath进行计算的结果等等。

在此查询中使用问题的样本数据(与原始查询相比略有更改):

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

我恰好得到123

此外,该解决方案可以防止依赖项循环中的无限循环。但是,它不会检测到它,因为它不会并且无法显示有一个循环,它只是防止了无限循环。

答案 3 :(得分:0)

您可以使用此功能查找重复值

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