如何避免在递归CTE中使用不允许的外连接?

huangapple go评论121阅读模式
英文:

How to avoid prohibited outer join in recursive CTE?

问题

我有一个表示对象之间无环依赖关系的表:

CREATE TEMPORARY TABLE dependencies
(
    obj_id        bigint,
    depended_upon bigint NULL
);
INSERT INTO dependencies
VALUES
    (1, NULL), -- 1不依赖于任何其他对象
    (2, 1),    -- 1依赖于2
    (3, 4),    -- 4依赖于3
    (3, 2),    -- ...2也是如此
    (4, NULL); -- 4也不依赖于任何其他对象

我想确定从叶子节点开始修剪对象以确保不违反依赖关系的顺序。对于这个示例,结果如下所示:

对象ID 修剪步骤
4 0
1 0
2 1
3 2

每个对象在没有其他对象依赖于它的第一步中被修剪。

我尝试使用递归CTE来实现这个目标:

WITH RECURSIVE deletion_order(obj_id, step) AS (
    SELECT obj_id, 0
    FROM dependencies
    GROUP BY obj_id
    HAVING COUNT(depended_upon) = 0
    UNION ALL
    SELECT dependencies.obj_id, step + 1
    FROM dependencies
             LEFT JOIN deletion_order ON dependencies.depended_upon = deletion_order.obj_id
    WHERE deletion_order.obj_id IS NULL
)
SELECT *
FROM deletion_order
ORDER BY step;

但Postgres报错:

> [42P19] ERROR: recursive reference to query "deletion_order" must not appear within an outer join

如何在不使用外连接的情况下实现这一目标?请注意,dependencies 的格式是灵活的,如果需要改变以适应解决方案,也是可以的。

英文:

I have a table which represents acyclic dependencies between objects:

CREATE TEMPORARY TABLE dependencies
(
    obj_id        bigint,
    depended_upon bigint NULL
);
INSERT INTO dependencies
VALUES
    (1, NULL), -- 1 is not depended upon by any other object
    (2, 1),    -- 1 depends on 2
    (3, 4),    -- 4 depends on 3
    (3, 2),    -- ... and so does 2
    (4, NULL); -- 4 also is not depended upon by any other object

I'd like to determine the order in which I would have to prune objects so that no dependencies are violated, starting from the leaves. For this example, that result would look like this:

Object ID Prune step
4 0
1 0
2 1
3 2

Each object is pruned in the first step in which no other object depends on it.

I've tried to do this with a recursive CTE:

WITH RECURSIVE deletion_order(obj_id, step) AS (
    SELECT obj_id, 0
    FROM dependencies
    GROUP BY obj_id
    HAVING COUNT(depended_upon) = 0
    UNION ALL
    SELECT dependencies.obj_id, step + 1
    FROM dependencies
             LEFT JOIN deletion_order ON dependencies.depended_upon = deletion_order.obj_id
    WHERE deletion_order.obj_id IS NULL
)
SELECT *
FROM deletion_order
ORDER BY step;

But Postgres complains:

> [42P19] ERROR: recursive reference to query "deletion_order" must not appear within an outer join

How can I accomplish this without an outer join? Note that the format of dependencies is flexible, if it needs to change to accommodate the solution.

答案1

得分: 1

如果我理解你尝试实现的内容:你已经解决了大多数递归查询的问题,但在最简单的地方出现了问题:

  1. 你的CTE的初始化可以更简单,使用 WHERE depended_upon IS NULL
  2. 我不理解为什么你在CTE的递归部分中加入了 WHERE deletion_order.obj_id IS NULL
  3. 连接应该是内连接,我不明白为什么你认为需要外连接(我猜这与前一个问题有关,但这没有帮助我理解)。

最终的查询:

WITH RECURSIVE deletion_order(obj_id, step) AS (
    SELECT obj_id, 0
    FROM dependencies
    WHERE depended_upon IS NULL
    UNION ALL
    SELECT dependencies.obj_id, step + 1
    FROM dependencies
    JOIN deletion_order ON dependencies.depended_upon = deletion_order.obj_id
)
SELECT obj_id, MAX(step) AS MaxStep /*, MIN(step) AS MinStep(可选,见下面的注释)*/
FROM deletion_order
ORDER BY MaxStep;

所以我不知道如何回答你的问题(如何避免外连接),因为你本来不应该觉得需要在首次使用外连接。

我之前回答了一个关于如何构建递归CTE的问题,也许值得查看。

英文:

If I understood what you are trying to achieve: you have most of the recursive queries figured out but somehow failed where it was the easiest:

  1. The initialization of your CTE could be easier with WHERE depended_upon IS NULL.
  2. I did not understand why you had put WHERE deletion_order.obj_id IS NULL in the recursive part of the CTE.
  3. The JOIN should be an inner join, I did not understand why you thought you needed an outer join (I guess it is linked with the previous point but that did not help me understand).

Resulting query:

WITH RECURSIVE deletion_order(obj_id, step) AS (
    SELECT obj_id, 0
    FROM dependencies
    WHERE depended_upon IS NULL
    UNION ALL
    SELECT dependencies.obj_id, step + 1
    FROM dependencies
    JOIN deletion_order ON dependencies.depended_upon = deletion_order.obj_id
)
SELECT obj_id, MAX(step) AS MaxStep /*, MIN(step) AS MinStep (optional, see comment below)*/
FROM deletion_order
ORDER BY MaxStep;

So I do not know how to answer your question (How to avoid outer join) because you should not have felt the need to use an outer join in the first place.

I have answered a previous question as how to build a recursive CTE here. Maybe it is worth checking.

huangapple
  • 本文由 发表于 2023年2月8日 09:57:58
  • 转载请务必保留本文链接:https://go.coder-hub.com/75380704.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定