2014-09-17 3 views
1

Скажет, у меня есть древовидная таблица областей:SQL сервер - траверс дерева в двух направлениях

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

Теперь я хочу, чтобы управлять правами доступа на любом узле в дереве. Права доступа относятся к документам и пользователям. Я понял, что я могу использовать рекурсивный CTE для получения всех узлов на или ниже данного узла. Например, пользователи, имеющие доступ к «Германии», должны иметь доступ ко всем документам, совместно используемым «Германия», «Кёльн» или «Берлин», что достаточно просто.

Однако я потерялся, когда хочу пересечь дерево в обоих направлениях. Пользователь в Германии также должен увидеть документ, который делится с «Европой». Пользователь в Кёльне должен иметь возможность видеть «Кёльн», «Германия» и «Европа», но не «Берлин».

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 

Этот запрос дает «Германия», «Кельн», «Берлин», начиная с узла «Германия» идет вверх, но я хотел бы выполнить запрос, который дает все эти узлы, а также любое количество узлы растут, но не разветвляются, из «Германии».

Должен ли я создать два CTE и запросить оба?

ответ

2

Вы можете выполнить команду UNION на 2 CTE результат, который работает как вверх, так и вниз по дереву.

Так присоединиться к CTE1 будет:

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

И присоединиться на CTE2 будет наоборот работать в обратном направлении:

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

Так, выполняющей 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 
Смежные вопросы