问题描述
背景
我在 CentOS 7 上运行 Postgres 11。
由于 S-Man 对 my recent question 的回答,我最近学习了 Postgres 中递归 CTE 的基础知识。
问题
在处理一个密切相关的问题(计算捆绑和装配中销售的零件)并使用这种递归 CTE 时,我遇到了一个问题,即查询无限循环并且永远不会完成。
我将此追溯到 relator
表中存在非虚假的“自引用”条目,即 parent_name
和 child_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对于child1和child2是相同的 在 relator 表中,child1 和 child2
的 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 描述了实现:
-
评估非递归项。对于
UNION
(但不是UNION ALL
),丢弃重复的行。在递归查询的结果中包含所有剩余的行,并将它们放入临时工作表中。 -
只要工作表不为空,重复以下步骤:
-
评估递归项,用工作表的当前内容代替递归自引用。对于
UNION
(但不是UNION ALL
),丢弃重复的行和与任何先前结果行重复的行。在递归查询的结果中包含所有剩余的行,并将它们放入临时中间表中。 -
用中间表的内容替换工作表的内容,然后清空中间表。
-
“摆脱重复”应该会导致中间表在某个时候为空,从而结束迭代。