【问题标题】:Recursive query used for transitive closure用于传递闭包的递归查询
【发布时间】:2015-09-28 07:27:46
【问题描述】:

我创建了一个简单的示例来说明在 PostgreSQL 中使用递归查询的传递闭包。

但是,我的递归查询有些问题。我还不熟悉语法,所以这个请求对我来说可能完全是noobish,为此我提前道歉。如果您运行查询,您将看到节点 1 在路径结果中重复出现。有人可以帮我弄清楚如何调整 SQL 吗?

/*           1
           /   \
          2     3
         / \   /
        4  5  6
       /
      7
     / \
    8   9
*/

create table account(
acct_id INT,
parent_id INT REFERENCES account(acct_id),
acct_name VARCHAR(100),
PRIMARY KEY(acct_id)
);

insert into account (acct_id, parent_id, acct_name) values (1,1,'account 1');
insert into account (acct_id, parent_id, acct_name) values (2,1,'account 2');
insert into account (acct_id, parent_id, acct_name) values (3,1,'account 3');
insert into account (acct_id, parent_id, acct_name) values (4,2,'account 4');
insert into account (acct_id, parent_id, acct_name) values (5,2,'account 5');
insert into account (acct_id, parent_id, acct_name) values (6,3,'account 6');
insert into account (acct_id, parent_id, acct_name) values (7,4,'account 7');
insert into account (acct_id, parent_id, acct_name) values (8,7,'account 8');
insert into account (acct_id, parent_id, acct_name) values (9,7,'account 9');

WITH RECURSIVE search_graph(acct_id, parent_id, depth, path, cycle) AS (
        SELECT g.acct_id, g.parent_id, 1,
          ARRAY[g.acct_id],
          false
        FROM account g
      UNION ALL
        SELECT g.acct_id, g.parent_id, sg.depth + 1,
          path || g.acct_id,
          g.acct_id = ANY(path)
        FROM account g, search_graph sg
        WHERE g.acct_id = sg.parent_id AND NOT cycle
)
SELECT path[1] as Child,parent_id as Parent,path || parent_id as path FROM search_graph
ORDER BY path[1],depth;

【问题讨论】:

  • 您的测试设置可靠且有用,但请解释一下您的结果应该是什么样子?折腾关键词transitive closure是不够的解释。

标签: sql postgresql common-table-expression recursive-query transitive-closure-table


【解决方案1】:

您可以在几个地方进行简化(假设acct_idparent_idNOT NULL):

WITH RECURSIVE search_graph AS (
   SELECT parent_id, ARRAY[acct_id] AS path
   FROM   account

   UNION  ALL
   SELECT g.parent_id, sg.path || g.acct_id
   FROM   search_graph sg
   JOIN   account g ON g.acct_id = sg.parent_id 
   WHERE  g.acct_id <> ALL(sg.path)
   )
SELECT path[1] AS child
     , path[array_upper(path,1)] AS parent
     , path
FROM   search_graph
ORDER  BY path;
  • acct_iddepthcycle 列只是您查询中的噪音。
  • WHERE 条件必须提前一步退出递归,顶部节点的重复条目在结果中。那是您原版中的“不合时宜”。

剩下的就是格式化了。

如果您知道您的图表中唯一可能的圆圈是自引用,我们可以更便宜:

WITH RECURSIVE search_graph AS (
   SELECT parent_id, ARRAY[acct_id] AS path, acct_id <> parent_id AS keep_going
   FROM   account

   UNION  ALL
   SELECT g.parent_id, sg.path || g.acct_id, g.acct_id <> g.parent_id
   FROM   search_graph sg
   JOIN   account g ON g.acct_id = sg.parent_id 
   WHERE  sg.keep_going
)
SELECT path[1] AS child
     , path[array_upper(path,1)] AS parent
     , path
FROM   search_graph
ORDER  BY path;

SQL Fiddle.

请注意,带有修饰符(如varchar(5))的数据类型会出现问题(至少到 pg v9.4),因为数组连接会丢失修饰符,但 rCTE 坚持要求类型完全匹配:

【讨论】:

  • 非常感谢您的周到回复!两个查询(甚至是后者)都完全按照我的预期工作。
【解决方案2】:

您已将帐户 1 设置为其自己的父帐户。如果将该帐户的父级设置为null,则可以避免将该帐户同时作为起始节点和结束节点(设置逻辑的方式将包含一个循环,但不会添加到该循环中,这似乎是合理的)。将最终的“路径”列更改为 case when parent_id is not null then path || parent_id else path end 之类的内容看起来也更好一些,以避免最后出现空值。

【讨论】:

  • 您运行了我的样本并确认了吗?当我将帐户 1 的插入更改为 null 父级时,它并没有改善结果。
猜你喜欢
  • 1970-01-01
  • 2013-07-23
  • 1970-01-01
  • 2017-12-27
  • 1970-01-01
  • 1970-01-01
  • 2014-06-11
  • 2016-12-11
  • 1970-01-01
相关资源
最近更新 更多