确保递归查询的唯一性

时间:2015-03-25 13:00:00

标签: sql postgresql

我有一个非常基本的父子/树层次结构和一个递归查询,它还添加了depth并且几乎完全加载了所有内容。当我尝试加载多个节点时,其中一个节点是另一个节点的子节点,我得到重复的行(因为depth得到更新,行不再相同)。

我确实阅读了documentation,我没有使用UNION ALL,我确实尝试了文档中的NOT cycle技巧,我知道ltree数据类型,但是我不能用它。这是另外一点,请考虑以下树:

5
├─9
│ └─15
├─10
│ └─16
└─11
  └─17

查询:

WITH RECURSIVE "CTE" AS
(
    SELECT "id", 0 AS "depth"
    FROM "Node" WHERE "id" IN (5, 9, 15)
    UNION
    SELECT "Node"."id", "CTE"."depth" + 1
    FROM "CTE" JOIN "Node" ON "Node"."parentId" = "CTE"."id"
)
SELECT *
FROM "CTE"
ORDER BY "id";

结果是:

id  depth 
5   0
9   0
9   1
10  1
11  1
15  0
15  1
15  2
16  2
17  2

取代期望的结果:

id  depth 
5   0
9   0
10  1
11  1
15  0
16  2
17  2

使用WHERE "id" = 5运行相同的查询会产生这种情况(请注意,深度有何不同,因为选择是从根目录开始的):

id  depth 
5   0
9   1
10  1
11  1
15  2
16  2
17  2

解决方法是将join修改为:

FROM "CTE" JOIN "Node" ON 
    "Node"."parentId" = "CTE"."id" AND 
    "Node"."id" NOT IN (SELECT "id" FROM "CTE")

但Postgres不允许从子查询引用“CTE”。我想知道是否有正确解决这个问题的方法?

顺便说一下,我确实提出了一个有效的解决方案,我在几个不同的场景中尝试过,但我并不是100%肯定它会在所有情况下都有效。它基本上消除了最初选择的值,确保迭代不会“输入”它们。我是否正确地使用它/这种方法有任何陷阱吗?

WITH RECURSIVE "CTE" AS
(
    SELECT "id", 0 AS "depth"
    FROM "Node" WHERE "id" IN (5, 9, 15)
    UNION
    SELECT "Node"."id", "CTE"."depth" + 1
    FROM "CTE" JOIN "Node" ON 
        "Node"."parentId" = "CTE"."id" 
        AND NOT IN (5, 9, 15)
)
SELECT *
FROM "CTE"
ORDER BY "id";

1 个答案:

答案 0 :(得分:1)

-- Data
CREATE TABLE node
        ( id integer NOT NULL PRIMARY KEY
        , parentid integer REFERENCES node(id)
        );

INSERT INTO node(id,parentid) VALUES
(5, NULL)
, (9,5), (10,5), (11,5)
, (15,9), (16,10), (17,11)
        ;

-- query
WITH RECURSIVE tree AS (
    SELECT id, 0 AS depth
    FROM node WHERE id IN (5, 9, 15)
    UNION
    SELECT node.id, tree.depth + 1
    FROM tree JOIN node ON node.parentid = tree.id
    )
SELECT *
FROM tree tr
WHERE NOT EXISTS ( -- trivial way to suppress duplicates with longer path
        SELECT *
        FROM tree nx
        WHERE nx.id = tr.id
        AND nx.depth < tr.depth
        )
ORDER BY id
        ;

更新:这看起来成本更低。这对于给定的数据是正确的(但在一般情况下不是IIUC):

WITH RECURSIVE tree AS (
    SELECT id, 0 AS depth
    FROM node WHERE id IN (5, 9, 15)
    UNION
    SELECT node.id, tree.depth + 1
    FROM tree JOIN node ON node.parentid = tree.id
    WHERE node.id NOT IN (5, 9, 15)
    )
SELECT *
FROM tree tr
ORDER BY id
        ;