当前位置: 首页 > 面试题库 >

在递归CTE中检测重复项

马泰
2023-03-14
问题内容

我在数据库中存储了一组依赖项。我正在寻找直接或间接依赖于当前对象的所有对象。由于对象可以依赖零个或多个其他对象,因此完全可以合理地认为对象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 + 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,但我不需要两个分支都可以-
第一个分支可以转到5,而另一个可以停在该位置。然后我们继续进行6和7。大多数循环检测将找不到任何循环,并两次返回5、6、7。考虑到我希望我的大多数生产数据都具有0-3个立即引用,并且大多数情况类似,因此从一个对象到另一个对象有多个分支是很普遍的,而向下跳转这些分支将不会只是多余的,但是浪费大量的时间和资源。


问题答案:

您可以使用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



 类似资料:
  • 问题内容: 假设具有以下CTE,这些CTE返回我已经拥有的某些树数据(邻接模型)的级别(取自Linq中的分层数据- options和performance): 我想知道通过使用C#而不是SQL进行递归是否会提高性能。假设我有一个IQueryable,其中Tree是表示层次结构表中条目的实体,谁能向我展示如何执行CTE与递归C#函数相同的工作?类似于以下内容: 看到使用lambda表达式很容易做到这

  • 问题内容: 在这个sqlfiddle中… http://sqlfiddle.com/#!6/b6587/6 我收到以下错误…。 声明终止。在语句完成之前,最大递归100已用尽。 我知道CTE第二选择的where子句中需要进行“终止检查”。即使您取消注释WHERE子句,我也会遇到相同的错误。 我只是想了解1)为什么根本需要它……毕竟每个订单行都与每个客户行都有关系,2)由于需要“终止检查”,因此该示

  • 问题内容: 我正在尝试执行我认为使用CTE进行递归比较困难的事情是SQL Server 2008。 在下面的示例中,您可以假设固定深度为3 …没有任何比这更低的深度了。在现实生活中,深度是“更深的”,但仍然是固定的。在示例中,我尝试将其简化一些。 我的输入数据如下。 我的CTE的输出应为下表。 如果我可以在输出中获得ID列,则可以肯定地可以映射到查找表中的名称。 我也乐于接受其他方法来完成此任务,

  • 问题内容: 此查询生成从1到4的数字。 但是,如果我对此进行修改, 它给 错误:“ z”处或附近的语法错误 我在这里做错了什么? 问题答案: 我认为这是因为RECURSIVE是WITH语句的修饰符,而不是常用表表达式的属性,因此您可以像这样使用它:

  • 问题内容: 我不是SQL专家,但是如果有人可以帮助我。 我使用递归CTE来获取如下值。 Child1 –> Parent 1 Parent1 –> Parent 2 Parent2 –> NULL 如果数据填充出错,那么我将遇到以下类似情况,因此CTE可能会进入无限递归循环并给出最大递归错误。由于数据量很大,因此我无法手动检查此 错误数据 。请让我知道是否有办法找到它。 Child1 –> Par

  • 问题内容: 我的PostgreSQL数据库中有一个有向图,节点和循环之间可以有多个路径: 我想找到一个节点和与其连接的每个节点之间的最小边数: 返回所有路径的深度: 我需要最低限度,但 递归查询的递归项中不允许使用聚合函数 但是,对结果使用聚合函数可以: 预期回报 但是,进入循环会导致无限循环,并且 对查询“节点”的递归引用一定不能出现在子查询中 ,因此我无法检查是否已访问节点: 我在这里寻找的结