Рекурсивный SQL для создания меню навигации DNN - PullRequest
1 голос
/ 08 декабря 2010

Я работаю над модулем DotNetNuke, который включает в себя древовидное меню навигации.

Пока у меня это работает, в том смысле, что дочерние узлы связаны с их правильными родительскими узлами, но братья и сестры узлов все еще не в порядке. Есть поле с именем TabOrder, используемое для определения порядка братьев и сестер, но из-за рекурсии я не могу их правильно отсортировать.

Я пытаюсь сделать это в хранимой процедуре SQL Server, что может быть ошибкой, но я чувствую, что настолько близок, что должно быть решение. Кто-нибудь знает, что я делаю не так?

Буду признателен за любые ваши идеи. Заранее спасибо.


Решение:

Я наконец нашел решение своего вопроса. Ключом было рекурсивное создание Линии Вкладок (TabLevel + TabOrder) от Корневой вкладки до вкладок Листа. Как только это было создано, я смог правильно упорядочить возвращенные записи.

Однако, когда я возвращался, чтобы опубликовать это, я увидел ответ MarkXA, который, вероятно, является лучшим решением. Я не знал, что метод GetNavigationNodes вообще существует.

Я думаю, он прав, что использование GetNavigationNodes является более перспективным решением, но пока я буду использовать свое решение на основе SQL. --Что я могу сказать? Я учусь трудному пути.

Вот оно:

ALTER procedure [dbo].[Nav_GetTabs]
    @CurrentTabID   int = 0
AS

--============================================================
--create and populate @TabLineage table variable with Tab Lineage
--
--"Lineage" consists of the concatenation of TabLevel & TabOrder, concatenated recursively from the root to leaf.
--The lineage is VERY important, making it possible to properly order the Tab links in the navigation module.
--This will be used as a lookup table to match Tabs with their lineage.
--============================================================
DECLARE @TabLineage table
    (
        TabID       int,
        Lineage     varchar(100)
    );

WITH TabLineage AS
(
    --start with root Tabs
    SELECT T.TabID, T.ParentID, CAST(REPLICATE('0', 5 - LEN(CAST(T2.[Level] as varchar(10)) + CAST(T2.TabOrder as varchar(10)))) + CAST(T2.[Level] as varchar(10)) + CAST(T2.TabOrder as varchar(10)) as varchar(100)) AS Lineage
        FROM Tabs T 
            INNER JOIN Tabs T2 ON T.TabID = T2.TabID
            INNER JOIN TabPermission TP ON T.TabID = TP.TabID
        WHERE T.ParentID IS NULL
            AND T.IsDeleted = 0 
            AND T.IsVisible = 1 
            AND TP.RoleID = -1

    UNION ALL

    --continue recursively, from parent to child Tabs
    SELECT T.TabID, T.ParentID, CAST(TL.Lineage + REPLICATE('0', 5 - LEN(CAST(T2.[Level] as varchar(10)) + CAST(T2.TabOrder as varchar(10)))) + CAST(T2.[Level] as varchar(10)) + CAST(T2.TabOrder as varchar(10)) as varchar(100)) AS Lineage
        FROM Tabs T
            INNER JOIN Tabs T2 ON T.TabID = T2.TabID
            INNER JOIN TabPermission TP ON T.TabID = TP.TabID
            INNER JOIN TabLineage TL ON T.ParentID = TL.TabID
        WHERE T.IsDeleted = 0 
            AND T.IsVisible = 1 
            AND TP.RoleID = -1
)
--insert results of recursive query into temporary table
INSERT @TabLineage
    SELECT TL.TabID, TL.Lineage FROM TabLineage TL ORDER BY TL.Lineage
    OPTION (maxrecursion 10);   --to increase number of traversed generations, increase "maxrecursion"


--============================================================
--create and populate @Ancestor table variable with @CurrentTab ancestors
--
--"Ancestors" are Tabs following the path from @CurrentTab to the root Tab it's descended from (inclusively).
--These are Tab links we want to see in the navigation.
--============================================================
DECLARE @Ancestor   table
    (
        TabID       int
    );

WITH Ancestor AS
(
    --start with @CurrentTab
    SELECT T.TabID, T.ParentID FROM Tabs T WHERE T.TabID = @CurrentTabID

    UNION ALL

    --continue recursively, from child to parent Tab
    SELECT T.TabID, T.ParentID
        FROM Ancestor A INNER JOIN Tabs T ON T.TabID = A.ParentID
)

--insert results of recursive query into temporary table
INSERT @Ancestor
    SELECT A.TabID FROM Ancestor A
    OPTION (maxrecursion 10);   --to increase number of traversed generations, increase "maxrecursion"


--============================================================
--retrieve Tabs to display in navigation

--This section UNIONs three query results together, giving us what we want:
-- 1. All Tabs at Level 0.
-- 2. All Tabs in @CurrentTab's lineage.
-- 3. All Tabs which are children of Tabs in @CurrentTab's lineage.
--============================================================
WITH TabNav (TabID, TabLevel, TabName, Lineage) AS
(
    --retrieve all Tabs at Level 0 -- (Root Tabs)
    (SELECT T.TabID, T.[Level] AS TabLevel, T.TabName, TL.Lineage
    FROM Tabs T 
        INNER JOIN TabPermission TP ON (T.TabID = TP.TabID AND TP.RoleID = -1)
        INNER JOIN @TabLineage TL ON T.TabID = TL.TabID
    WHERE T.IsDeleted = 0 
        AND T.IsVisible = 1 
        AND T.[Level] = 0

    UNION

    --retrieve Tabs in @CurrentTab's lineage
    SELECT T.TabID, T.[Level] AS TabLevel, T.TabName, TL.Lineage
    FROM Tabs T 
        INNER JOIN TabPermission TP ON (T.TabID = TP.TabID AND TP.RoleID = -1)
        INNER JOIN @Ancestor A ON T.TabID = A.TabID
        INNER JOIN @TabLineage TL ON T.TabID = TL.TabID
    WHERE T.IsDeleted = 0 
        AND T.IsVisible = 1 

    UNION

    --retrieve Tabs which are children of Tabs in @CurrentTab's lineage
    SELECT T.TabID, T.[Level] AS TabLevel, T.TabName, TL.Lineage
    FROM Tabs T 
        INNER JOIN TabPermission TP ON (T.TabID = TP.TabID AND TP.RoleID = -1)
        INNER JOIN @Ancestor A ON T.ParentID = A.TabID
        INNER JOIN @TabLineage TL ON T.TabID = TL.TabID
    WHERE T.IsDeleted = 0 
        AND T.IsVisible = 1)
)

--finally, return the Tabs to be included in the navigation module
SELECT TabID, TabLevel, TabName FROM TabNav ORDER BY Lineage;
--============================================================

Ответы [ 2 ]

2 голосов
/ 09 декабря 2010

Ответ "не используйте SQL". Уже есть метод DotNetNuke.UI.Navigation.GetNavigationNodes, который делает это за вас, и если вы его используете, ваш модуль не сломается, если и когда схема базы данных изменится. Даже если вам нужно сделать что-то, что GetNavigationNodes не будет обрабатывать, вам все равно лучше извлекать страницы через API, чтобы быть готовыми к будущему. Переход непосредственно к базе данных просто напрашивается на неприятности:)

1 голос
/ 08 декабря 2010

вот пример (не основанный на коде данного OP) примера рекурсивного дерева CTE, который показывает, как сортировать дерево:

DECLARE @Contacts table (id varchar(6), first_name varchar(10), reports_to_id varchar(6))
INSERT @Contacts VALUES ('1','Jerome', NULL )  -- tree is as follows:
INSERT @Contacts VALUES ('2','Joe'   ,'1')     --                      1-Jerome
INSERT @Contacts VALUES ('3','Paul'  ,'2')     --                     /        \
INSERT @Contacts VALUES ('4','Jack'  ,'3')     --              2-Joe           9-Bill
INSERT @Contacts VALUES ('5','Daniel','3')     --            /       \              \
INSERT @Contacts VALUES ('6','David' ,'2')     --     3-Paul          6-David       10-Sam
INSERT @Contacts VALUES ('7','Ian'   ,'6')     --    /      \            /    \
INSERT @Contacts VALUES ('8','Helen' ,'6')     -- 4-Jack  5-Daniel   7-Ian    8-Helen
INSERT @Contacts VALUES ('9','Bill ' ,'1')     --
INSERT @Contacts VALUES ('10','Sam'  ,'9')     --

DECLARE @Root_id  varchar(6)

--get all nodes 2 and below
SET @Root_id=2
PRINT '@Root_id='+COALESCE(''''+@Root_id+'''','null')
;WITH StaffTree AS
(
    SELECT 
        c.id, c.first_name, c.reports_to_id, c.reports_to_id as Manager_id, cc.first_name AS Manager_first_name, 1 AS LevelOf
        FROM @Contacts                  c
            LEFT OUTER JOIN @Contacts  cc ON c.reports_to_id=cc.id
        WHERE c.id=@Root_id OR (@Root_id IS NULL AND c.reports_to_id IS NULL)
    UNION ALL
        SELECT 
            s.id, s.first_name, s.reports_to_id, t.id, t.first_name, t.LevelOf+1
        FROM StaffTree            t
            INNER JOIN @Contacts  s ON t.id=s.reports_to_id
    WHERE s.reports_to_id=@Root_id OR @Root_id IS NULL OR t.LevelOf>1
)
SELECT * FROM StaffTree ORDER BY LevelOf, first_name

ВЫХОД:

@Root_id='2'
id     first_name reports_to_id Manager_id Manager_first_name LevelOf
------ ---------- ------------- ---------- ------------------ -----------
2      Joe        1             1          Jerome             1
6      David      2             2          Joe                2
3      Paul       2             2          Joe                2
5      Daniel     3             3          Paul               3
8      Helen      6             6          David              3
7      Ian        6             6          David              3
4      Jack       3             3          Paul               3

(7 row(s) affected)

Ключ - столбец LevelOf.Посмотрите, как это просто литерал 1 при выборе основного родителя в CTE.Затем столбец LevelOf увеличивается в части UNION ALL рекурсивного CTE.Каждый рекурсивный вызов (не строка) в CTE будет вызывать этот UNION ALL один раз и приращение.Не намного больше, чем это.

...