【问题标题】:Detect cycles using sql使用 sql 检测循环
【发布时间】:2016-09-18 23:20:54
【问题描述】:

我想检测层次结构中的潜在循环。我有三个表,每个表都有一个父列和一个子列:

Table1 包含一些节点(在子列中)及其父节点(在父列中); Table2 包含 Table1 的所有父级(在 child 列中)及其父级(在 parent 列中),依此类推。

例如,如果 A 是 B 的孩子,B 是 C 的孩子,C 是 A 的孩子,那么我有一个循环。

是否可以使用 sql 命令检测循环?

【问题讨论】:

标签: sql postgresql hierarchical-data


【解决方案1】:

这是一个适用于任意深度的解决方案。

将所有关系存储在一张表中:

   Table t
Parent | Child
------ | -----
B      | A
C      | B
A      | C
E      | D
F      | E

然后您可以使用此WITH RECURSIVE 查询来查找循环:

WITH RECURSIVE working(parent, last_visited, already_visited, cycle_detected) AS (
  SELECT parent, child, ARRAY[parent], false FROM t
  UNION ALL
  SELECT t.parent, t.child, already_visited || t.parent, t.parent = ANY(already_visited)
  FROM t
  JOIN working ON working.last_visited = t.parent
  WHERE NOT cycle_detected
)
SELECT parent, already_visited FROM working WHERE cycle_detected

Fiddle

它将为您提供属于循环一部分的parents,以及它们所在的循环:

A | A,C,B,A
B | B,A,C,B
C | C,B,A,C

它是这样工作的(因为这是关键字 RECURSIVE 指示 Postgres 做的事情):

  1. 运行第一个SELECT,从表t 中选择所有条目并将它们放在一个名为working 的临时表中。
  2. 然后运行第二个SELECT,将working 表与t 表连接起来以查找每个条目的子项。这些孩子被添加到已经见过的孩子数组中。
  3. 现在,只要将条目添加到 working 表中,就一次又一次地运行第二个 SELECT
  4. 当其中一个条目访问它之前访问过的子项 (t.parent = ANY(already_visited)) 时检测到一个循环,在这种情况下,cycle_detected 设置为 true,并且不再向条目添加子项。

【讨论】:

  • 感谢您的精彩内容和全面的解释。非常有用。
  • 另外,这里是 Postgres 官方文档,我相信它很好地解释了 RECURSIVE 查询的工作原理。 postgresql.org/docs/current/…
  • 很快 PostgreSQL 将支持CYCLE 子句来简化流程
【解决方案2】:

按照您现在构建表格的方式,以下 SQL 应该可以工作:

SELECT * FROM Table1
INNER JOIN Table2 on Table1.child = Table2.parent
INNER JOIN Table3 on Table2.child = Table3.parent
WHERE Table1.parent = Table3.child;

【讨论】:

  • 如果您的查询没有返回任何内容,这意味着没有循环,对吧?
  • 是的。它应该返回所有现有的循环,所以如果它什么都不返回,那么就没有循环。
  • 非常感谢!
【解决方案3】:

您的任务中的表之间存在非常奇怪的引用。然而,这是我检查现有循环的方法。

表 1 示例:

CREATE OR REPLACE FUNCTION fn_table1_check() RETURNS trigger
LANGUAGE plpgsql
AS $$
DECLARE
BEGIN
  PERFORM 1 FROM table2
    JOIN table3 ON table3.parent=table2.child
  WHERE table2.parent=NEW.child AND table3.child=NEW.parent
  LIMIT 1;
  IF FOUND THEN
    RAISE EXCEPTION 'Found recursive loop!';
  END IF;
  RETURN NEW;
END;
$$;
CREATE TRIGGER tg_table1_check BEFORE INSERT OR UPDATE ON table1 FOR EACH ROW EXECUTE PROCEDURE fn_table1_check();

【讨论】:

  • 非常感谢!我不熟悉plpgsql。我会研究你的代码。
猜你喜欢
  • 2011-11-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-06-15
  • 1970-01-01
  • 2010-10-21
  • 2020-05-04
相关资源
最近更新 更多