【问题标题】:Query for all paths to all nodes in closure table with multiple parents查询具有多个父节点的闭包表中所有节点的所有路径
【发布时间】:2021-06-15 14:22:56
【问题描述】:

我有一个闭包表,其中可能有多个父级,关联的数据表如下所示:

CREATE TABLE [data] (
    id int IDENTITY(1,1) PRIMARY KEY,
    name VARCHAR(100)
)

CREATE TABLE closure (
    id int IDENTITY(1,1) PRIMARY KEY,
    [src_id] int,
    [dst_id] int,
    [depth] int,
    FOREIGN KEY ([src_id]) REFERENCES [data](id),
    FOREIGN KEY ([dst_id]) REFERENCES [data](id)
)

现在我创建了一些看起来像这样的测试数据,DDL 看起来像这样:

数据表

INSERT [data] ([name]) VALUES (N'data1')
INSERT [data] ([name]) VALUES (N'data2')
INSERT [data] ([name]) VALUES (N'data3')
INSERT [data] ([name]) VALUES (N'data4')
INSERT [data] ([name]) VALUES (N'data5')
INSERT [data] ([name]) VALUES (N'data6')

闭包表

INSERT [closure] ([src_id], [dst_id], [depth]) VALUES (1, 1, 0)
INSERT [closure] ([src_id], [dst_id], [depth]) VALUES (2, 2, 0)
INSERT [closure] ([src_id], [dst_id], [depth]) VALUES (3, 3, 0)
INSERT [closure] ([src_id], [dst_id], [depth]) VALUES (4, 4, 0)
INSERT [closure] ([src_id], [dst_id], [depth]) VALUES (5, 5, 0)
INSERT [closure] ([src_id], [dst_id], [depth]) VALUES (6, 6, 0)
INSERT [closure] ([src_id], [dst_id], [depth]) VALUES (1, 2, 1)
INSERT [closure] ([src_id], [dst_id], [depth]) VALUES (2, 5, 1)
INSERT [closure] ([src_id], [dst_id], [depth]) VALUES (1, 5, 2)
INSERT [closure] ([src_id], [dst_id], [depth]) VALUES (5, 6, 1)
INSERT [closure] ([src_id], [dst_id], [depth]) VALUES (2, 6, 2)
INSERT [closure] ([src_id], [dst_id], [depth]) VALUES (1, 6, 3)
INSERT [closure] ([src_id], [dst_id], [depth]) VALUES (1, 3, 1)
INSERT [closure] ([src_id], [dst_id], [depth]) VALUES (3, 4, 1)
INSERT [closure] ([src_id], [dst_id], [depth]) VALUES (1, 4, 2)
INSERT [closure] ([src_id], [dst_id], [depth]) VALUES (4, 5, 1)
INSERT [closure] ([src_id], [dst_id], [depth]) VALUES (3, 5, 2)
INSERT [closure] ([src_id], [dst_id], [depth]) VALUES (1, 5, 3)
INSERT [closure] ([src_id], [dst_id], [depth]) VALUES (4, 6, 2)
INSERT [closure] ([src_id], [dst_id], [depth]) VALUES (3, 6, 3)
INSERT [closure] ([src_id], [dst_id], [depth]) VALUES (1, 6, 4)

现在我想要的是一个查询,它输出每个数据集的每个路径,直到该路径不再有src_id, 我想用这个语句查询:

;WITH hierarchy_cte AS (
    SELECT [data].id, [data].name, CAST ([data].name AS VARCHAR(100)) AS path , 0 as pathDepth  
    FROM closure
    INNER JOIN [data]
    ON [data].id = closure.src_id
    WHERE closure.depth = 0
    UNION ALL
    SELECT closure.src_id, hierarchy_cte.name, CAST(([srcdata].name + ' / ' + hierarchy_cte.path) AS VARCHAR(100)) , hierarchy_cte.pathDepth + 1
    FROM closure    
    INNER JOIN [data] srcdata
    ON srcdata.id = closure.src_id 
    INNER JOIN [data]
    ON [data].id = closure.dst_id
    inner join [hierarchy_cte]
    ON [data].id = hierarchy_cte.id
    AND closure.depth = 1
) 
SELECT * FROM hierarchy_cte

结果如下所示:

+----+-------+---------------------------------------+-----------+
| id | name  |                 path                  | pathDepth |
+----+-------+---------------------------------------+-----------+
|  1 | data1 | data1                                 |         0 |
|  2 | data2 | data2                                 |         0 |
|  3 | data3 | data3                                 |         0 |
|  4 | data4 | data4                                 |         0 |
|  5 | data5 | data5                                 |         0 |
|  6 | data6 | data6                                 |         0 |
|  5 | data6 | data5 / data6                         |         1 |
|  2 | data6 | data2 / data5 / data6                 |         2 |
|  4 | data6 | data4 / data5 / data6                 |         2 |
|  3 | data6 | data3 / data4 / data5 / data6         |         3 |
|  1 | data6 | data1 / data3 / data4 / data5 / data6 |         4 |
|  1 | data6 | data1 / data2 / data5 / data6         |         3 |
|  2 | data5 | data2 / data5                         |         1 |
|  4 | data5 | data4 / data5                         |         1 |
|  3 | data5 | data3 / data4 / data5                 |         2 |
|  1 | data5 | data1 / data3 / data4 / data5         |         3 |
|  1 | data5 | data1 / data2 / data5                 |         2 |
|  3 | data4 | data3 / data4                         |         1 |
|  1 | data4 | data1 / data3 / data4                 |         2 |
|  1 | data3 | data1 / data3                         |         1 |
|  1 | data2 | data1 / data2                         |         1 |
+----+-------+---------------------------------------+-----------+

我认为我的陈述将沿着所有可能的路径进行,并在没有更多 src_id 时结束。我只是卡住了。 我想要的结果是:

+----+-------+---------------------------------------+-----------+
| id | name  |                 path                  | pathDepth |
+----+-------+---------------------------------------+-----------+
|  1 | data1 | data1                                 |         0 |
|  2 | data2 | data1 / data2                         |         1 |
|  3 | data3 | data1 / data3                         |         1 |
|  4 | data4 | data1 / data3 / data4                 |         2 |
|  5 | data5 | data1 / data3 / data4 / data5         |         3 |
|  5 | data5 | data1 / data2 / data5                 |         2 |
|  6 | data6 | data1 / data3 / data4 / data5 / data6 |         4 |
|  6 | data6 | data1 / data2 / data5 / data6         |         3 |
+----+-------+---------------------------------------+-----------+

这个查询似乎并不难,但我就是想不通。

【问题讨论】:

    标签: sql sql-server tsql recursion common-table-expression


    【解决方案1】:

    你需要加入目的地,我在 CTE 中添加了closure.dst_id

    然后我从 hierarchy_cte 中选择所有内容,并将 dst_id 与 src_id 连接

    FROM hierarchy_cte
        inner join closure 
        on closure.src_id=hierarchy_cte.dst_id
        AND closure.depth = 1
    

    最后,我们只需要为 CTE 的第一步选择根。让我们将根定义为不是闭包关系目的地的任何节点,

    select * from closure c1
    where depth=0
    and not exists(select 1 from closure c2 where c2.depth>=1 and c1.src_id=c2.dst_id)
    

    或许

    select * from closure c1
    where depth=0
    and not exists(select 1 from closure c2 where c2.src_id<>c2.dst_id and c1.src_id=c2.dst_id)
    

    使用这个我们得到这个最终查询:

    ;WITH hierarchy_cte AS (
        SELECT [data].id, [data].name, CAST ([data].name AS VARCHAR(100)) AS path , closure.depth as pathDepth, closure.dst_id
        FROM closure
        INNER JOIN [data]
        ON [data].id = closure.src_id
        WHERE closure.depth = 0
        and not exists(select 1 from closure c2 where c2.depth>=1 and closure.src_id=c2.dst_id)
        UNION ALL
        SELECT hierarchy_cte.id,data.name,CAST ( [hierarchy_cte].path+'/'+ [data].name AS VARCHAR(100)) AS path,pathDepth+1,closure.dst_id
        from [hierarchy_cte] 
        inner join closure 
        on closure.src_id=hierarchy_cte.dst_id
        and closure.depth=1
        INNER JOIN [data] srcdata
        ON srcdata.id = closure.src_id 
        INNER JOIN [data]
        ON [data].id = closure.dst_id
    ) 
    SELECT id,name,path,pathDepth FROM hierarchy_cte
    order by path
    

    【讨论】:

    • 这似乎没有给出我希望的结果。
    • 对不起 - 我试图清理它但搞砸了,试试我上面更新的查询
    • 好吧,这给出了正确的结果,但前提是您知道根是 data1。当有多个根并且没有给出根名时,查询也应该工作:/
    • 我不确定你的意思?我知道我在带有 path1 的注释 where 子句中留下了,那只是为了复制您拥有的部分列表。我从上面的查询中得到 21 条路径,其中 data1.data2,..,data6 充当 root。除此之外,您还希望从查询中得到什么答案?
    • 啊,我想知道你哪里弄错了。我给出的最后一个清单是我想要的确切结果。所以最后应该是8条路径。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-07
    • 1970-01-01
    • 1970-01-01
    • 2020-04-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多