如何阻止我的 Postgres 递归 CTE 无限循环?

问题描述

背景

我在 CentOS 7 上运行 Postgres 11。
由于 S-Man 对 my recent question 的回答,我最近学习了 Postgres 中递归 CTE 的基础知识。

问题

在处理一个密切相关的问题(计算捆绑和装配中销售的零件)并使用这种递归 CTE 时,我遇到了一个问题,即查询无限循环并且永远不会完成。

我将此追溯到 relator 表中存在非虚假的“自引用”条目,即 parent_namechild_name 具有相同值的行。

>

我知道这些是问题的根源,因为当我用测试表和数据重新创建这种情况时,当这些行存在时会发生不希望的循环行为,而当这些行不存在时就会消失 在 CTE 中使用 UNION(排除重复返回的行)而不是 UNION ALL 时。

我认为数据模型本身可能需要调整,以便不需要这些“自引用”行,但是现在,我需要做的是获取此查询以在完成时返回所需的数据并停止循环

我怎样才能达到这个结果?非常感谢所有指导!

表格和测试数据

CREATE TABLE the_schema.names_categories (
    id INTEGER NOT NULL PRIMARY KEY GENERATED ALWAYS AS IDENTITY,created_at TIMESTAMPTZ DEFAULT now(),thing_name TEXT NOT NULL,thing_category TEXT NOT NULL
);

CREATE TABLE the_schema.relator (
    id INTEGER NOT NULL PRIMARY KEY GENERATED ALWAYS AS IDENTITY,parent_name TEXT NOT NULL,child_name TEXT NOT NULL,child_quantity INTEGER NOT NULL 
);


/* NOTE: listing_name below is like an alias of a relator.parent_name as it appears in a catalog,required to know because it is these listing_names that are reflected by sales.sold_name */

CREATE TABLE the_schema.catalog_listings ( 
    id INTEGER NOT NULL PRIMARY KEY GENERATED ALWAYS AS IDENTITY,listing_name TEXT NOT NULL,parent_name TEXT NOT NULL
);

CREATE TABLE the_schema.sales (
    id INTEGER NOT NULL PRIMARY KEY GENERATED ALWAYS AS IDENTITY,sold_name TEXT NOT NULL,sold_quantity INTEGER NOT NULL
);

CREATE VIEW the_schema.relationships_with_child_category AS (
    SELECT 
    c.listing_name,r.parent_name,r.child_name,r.child_quantity,n.thing_category AS child_category
    FROM 
    the_schema.catalog_listings c
    INNER JOIN 
    the_schema.relator r 
    ON c.parent_name = r.parent_name
    INNER JOIN 
    the_schema.names_categories n 
    ON r.child_name = n.thing_name 
);

INSERT INTO the_schema.names_categories (thing_name,thing_category)
VALUES ('parent1','bundle'),('child1','assembly'),('child2',('subChild1','component'),('subChild2',('subChild3','component');

INSERT INTO the_schema.catalog_listings (listing_name,parent_name)
VALUES ('listing1','parent1'),('parent1','child1'),'child2'),'child2');

INSERT INTO the_schema.catalog_listings (listing_name,parent_name)
VALUES ('parent1','child2');


/* note the two 'self-referential' entries  */
INSERT INTO the_schema.relator (parent_name,child_name,child_quantity)
VALUES ('parent1','child1',1),'subChild1','subChild2',1)
('parent1','child2','subChild3',1);

INSERT INTO the_schema.sales (sold_name,sold_quantity)
VALUES ('parent1',2),('listing1',1);

当前查询,使用所需的 UNION ALL 无限循环

WITH RECURSIVE cte AS (
    SELECT 
        s.sold_name,s.sold_quantity,r.child_category as category
    FROM 
        the_schema.sales s
    JOIN the_schema.relationships_with_child_category r
    ON s.sold_name = r.listing_name

    UNION ALL
    
    SELECT
        cte.sold_name,cte.sold_quantity,r.child_category
    FROM cte
    JOIN the_schema.relationships_with_child_category r 
    ON cte.child_name = r.parent_name

)
SELECT
    child_name,SUM(sold_quantity * child_quantity)
FROM cte
WHERE category = 'component'
GROUP BY child_name
;

解决方法

catalog_listings表中,listing_name和parent_name对于child1child2是相同的 在 relator 表中,child1child2

的 parent_name 和 child_name 也相同

这些行正在创建循环递归。

只需从两个表中删除这两行:

delete from catalog_listings where id in (4,5)
delete from relator where id in (7,8)

那么你想要的输出如下:

child_name 总和
subChild2 8
subChild3 8
subChild1 16

这是您要寻找的结果吗?

如果您不能删除行,您可以使用下面添加 parent_namechild_name 条件来避免这些行:

WITH RECURSIVE cte AS (
    SELECT 
        s.sold_name,s.sold_quantity,r.child_name,r.child_quantity,r.child_category as category
    FROM 
        the_schema.sales s
    JOIN the_schema.relationships_with_child_category r
    ON s.sold_name = r.listing_name and r.parent_name <>r.child_name

    UNION ALL
    
    SELECT
        cte.sold_name,cte.sold_quantity,r.child_category
    FROM cte
    JOIN the_schema.relationships_with_child_category r 
    ON cte.child_name = r.parent_name and r.parent_name <>r.child_name

)
SELECT
    child_name,SUM(sold_quantity * child_quantity)
FROM cte
WHERE category = 'component'
GROUP BY child_name    ;
,

您可以简单地使用 UNION 而不是 UNION ALL 来避免无限递归。

The documentation 描述了实现:

  1. 评估非递归项。对于 UNION(但不是 UNION ALL),丢弃重复的行。在递归查询的结果中包含所有剩余的行,并将它们放入临时工作表中。

  2. 只要工作表不为空,重复以下步骤:

    1. 评估递归项,用工作表的当前内容代替递归自引用。对于 UNION(但不是 UNION ALL),丢弃重复的行和与任何先前结果行重复的行。在递归查询的结果中包含所有剩余的行,并将它们放入临时中间表中。

    2. 用中间表的内容替换工作表的内容,然后清空中间表。

“摆脱重复”应该会导致中间表在某个时候为空,从而结束迭代。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...