SQL服务器 - 遍历两个方向的树

时间:2014-09-17 12:50:08

标签: sql sql-server tree common-table-expression

假设我有一个树状结构的区域表:

id     parent_id      name
---------------------------------
1      null           Europe
2      1              Germany
3      2              Köln
4      2              Berlin
5      1              Norway

现在我想管理树中任何节点的访问权限。访问权限与文档和用户有关。我想我可以使用递归CTE来获取给定节点或其下的所有节点。例如,有权访问“德国”的用户应该可以访问与“德国”,“科隆”或“柏林”共享的所有文档,这很简单。

但是,当我想在两个方向上穿越树时,我迷失了方向。德国的用户还应该看到与“Europe”共享的文档。科隆的用户应该能够看到“Köln”,“德国”和“欧洲”,但不能看到“柏林”。

WITH RegionTree AS (
    SELECT topRegion.Id RootId, topRegion.Name, topRegion.Id
    FROM Region topRegion
    UNION ALL
    SELECT rt.RootId, r.Name, r.Id
    FROM Region r
    INNER JOIN RegionTree rt ON rt.Id = r.ParentId
)
SELECT rt.RootId, rt.Name, rt.Id
FROM RegionTree rt
WHERE rt.RootId = 2

此查询产生“德国”,“Köln”,“柏林”,从节点“德国”开始,但我想运行一个查询,产生所有这些节点,加上任意数量的节点,但不是从“德国”分支出来的。

我是否需要创建两个CTE并查询它们?

1 个答案:

答案 0 :(得分:3)

您可以在2 UNION结果集上执行CTE,该结果集可在树上上下运行。

因此,CTE1中的连接将是:

INNER JOIN RegionTree rt ON rt.Id = r.ParentId

CTE2的加入将是反过来的另一种方式:

INNER JOIN RegionTree2 rt ON rt.ParentId = r.Id

因此在CTE结果上执行UNION,(抱歉未经测试)

WITH RegionTree AS (
    SELECT topRegion.Id RootId, topRegion.Name, topRegion.Id
    FROM Region topRegion
    UNION ALL
    SELECT rt.RootId, r.Name, r.Id
    FROM Region r
    INNER JOIN RegionTree rt ON rt.Id = r.ParentId
),
RegionTree2 AS (
    SELECT topRegion.Id RootId, topRegion.Name, topRegion.Id
    FROM Region topRegion
    UNION ALL
    SELECT rt.RootId, r.Name, r.Id
    FROM Region r
    INNER JOIN RegionTree2 rt ON rt.ParentId = r.Id
)
SELECT rt.RootId, rt.Id, rt.Name, rt.CustomerId, rt.EntityId, rt.Depth
FROM RegionTree rt
WHERE rt.RootId = 2
UNION
SELECT rt.RootId, rt.Id, rt.Name, rt.CustomerId, rt.EntityId, rt.Depth
FROM RegionTree2 rt
WHERE rt.RootId = 2

虽然查看查询,但我可能会修改CTE以包含过滤条件而不是当前在WHERE子句中的条件,以防止查询比所需更多的数据。像这样:

DECLARE @ID INT = 2

WITH RegionTree AS (
    SELECT topRegion.Id RootId, topRegion.Name, topRegion.Id
    FROM Region topRegion
    WHERE topRegion.Id = @ID  --ADDED
    UNION ALL
    SELECT rt.RootId, r.Name, r.Id
    FROM Region r
    INNER JOIN RegionTree rt ON rt.Id = r.ParentId
    WHERE topRegion.Id IS NOT NULL -- ADDED
),
RegionTree2 AS (
    SELECT topRegion.Id RootId, topRegion.Name, topRegion.Id
    FROM Region topRegion
    WHERE topRegion.Id = @ID  --ADDED
    UNION ALL
    SELECT rt.RootId, r.Name, r.Id
    FROM Region r
    INNER JOIN RegionTree2 rt ON rt.ParentId = r.Id
    WHERE topRegion.Id IS NOT NULL -- ADDED
)
SELECT rt.RootId, rt.Id, rt.Name, rt.CustomerId, rt.EntityId, rt.Depth
FROM RegionTree rt
UNION
SELECT rt.RootId, rt.Id, rt.Name, rt.CustomerId, rt.EntityId, rt.Depth
FROM RegionTree2 rt