Microsoft SQL родительская проблема - PullRequest
2 голосов
/ 18 декабря 2010

Я довольно застрял, я хорошо осмотрелся, но я не совсем уверен, как я могу это сделать.

Мне нужно создать SP (TSQL), чтобы вернуть навигацию, но у меня есть несколько проблем с правильным упорядочением навигации.

Пример таблицы

NavID     OrderID     ParentID      NavName
1         1           0             Home
2         2           0             About
3         3           0             Contact Us
4         1           2             About Us Page
5         2           2             About Us Page 2
6         1           4             Another SubPage

Все, что мне нужно, это вернуть навигацию выше и одну навигацию ниже.

Так что, если я передам NavigationID 2, я ожидаю, что результаты вернутся так:

Home 
About
About Us Page 
About Us Page 2
Contact Us

Если я передам NavigationID 6, я ожидаю увидеть ..

Home
About
About Us Page
Another SubPage
About Us Page 2
Contact Us

Как вы можете видеть, он принимает во внимание OrderID, но сначала убедитесь, что у ребенка все в порядке.

Как мне этого добиться?

Ответы [ 4 ]

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

Вот полный скрипт, который делает то, что вам нужно (включая ваши тестовые данные):

DECLARE @nav TABLE (
            NavID INT NOT NULL PRIMARY KEY,
            OrderID INT NOT NULL,
            ParentID INT,
            NavName nvarchar(MAX) NOT NULL 
        ); 
INSERT @nav 
        SELECT 1, 1, 0, 'Home' UNION ALL 
        SELECT 2, 2, 0, 'About' UNION ALL 
        SELECT 3, 3, 0, 'Contact Us' UNION ALL 
        SELECT 4, 1, 2, 'About Us Page' UNION ALL 
        SELECT 5, 2, 2, 'About Us Page 2' UNION ALL 
        SELECT 6, 1, 4, 'Another SubPage'; 

DECLARE @NavigationID int; 
SET     @NavigationID = 2;

WITH Ancestors AS (          
    SELECT @NavigationID NavID
    UNION ALL 
    SELECT  n.ParentID
    FROM    @nav n 
    JOIN Ancestors a ON (n.NavID = a.NavID)
),
VisibleNav AS (
    SELECT  n.*, CONVERT(FLOAT, 1)/SUM(1) OVER (PARTITION BY n.ParentID) Mul, ROW_NUMBER() OVER (PARTITION BY n.ParentID ORDER BY n.OrderID)-1 Pos
    FROM @nav n 
    JOIN Ancestors a ON n.ParentID = a.NavID
),
SortedNav AS (
    SELECT vn.*, vn.Pos*vn.Mul Sort, 1 Depth
    FROM VisibleNav vn
    WHERE vn.ParentID = 0
    UNION ALL
    SELECT vn.NavID, vn.OrderID, vn.ParentID, vn.NavName, vn.Mul*sn.Mul, vn.Pos, vn.Pos*(vn.Mul*sn.Mul)+sn.Sort, sn.Depth + 1
    FROM VisibleNav vn
    JOIN SortedNav sn ON sn.NavID = vn.ParentID
)
SELECT sn.NavID, sn.OrderID, sn.ParentID, sn.NavName
FROM SortedNav sn
ORDER BY sn.Sort, sn.Depth;

По сути, у меня есть рекурсивный CTE для создания списка всех родителей, которые должны использоваться в вашей навигациивключая глубину родительского элемента (чтобы порядок не зависел от идентификаторов), а затем я присоединяюсь к элементам навигации по этому вопросу.

0 голосов
/ 18 декабря 2010

Мне немного неясно, какие именно узлы вы хотите отобразить ... но, если я правильно понимаю, главный вопрос здесь состоит в том, как упорядочить результирующие узлы. Основная трудность заключается в том, что критерии упорядочения имеют переменную длину: узел должен быть упорядочен на основе всей последовательности OrderId значений для узла и всех его предков. Например, последовательность заказа для узла 6 равна «2, 1, 1».

SQL плохо обрабатывает такие последовательности переменной длины. Я предлагаю использовать значение NVARCHAR (MAX) для последовательности упорядочения. Для узла 6 мы будем использовать «0002.0001.0001». В этом виде узлы можно упорядочить тривиально, используя сравнение строк. Обратите внимание, что значения идентификатора должны быть дополнены нулями, чтобы обеспечить правильное упорядочение (я произвольно выбрал заполнение до 4 цифр - для реального приложения может потребоваться другой выбор).

Так что это подводит нас к болтам. Мы начнем с создания таблицы с именем NavigationData для хранения наших тестовых данных:

SELECT NULL AS NavId, NULL AS OrderId, NULL AS ParentId, NULL AS NavName
  INTO NavigationData WHERE 1=0
UNION SELECT 1, 1, 0, 'Home'
UNION SELECT 2, 2, 0, 'About'
UNION SELECT 3, 3, 0, 'Contact Us'
UNION SELECT 4, 1, 2, 'About Us Page'
UNION SELECT 5, 2, 2, 'About Us Page 2'
UNION SELECT 6, 1, 4, 'Another SubPage'

Теперь мы создадим вспомогательное представление, которое для каждого возможного желаемого узла перечисляет все связанные узлы вместе с их вычисленными строками пути. Как я уже говорил в начале, я чувствую, что критерии выбора связанных узлов недостаточно заданы, поэтому может потребоваться скорректировать желаемое / связанное выражение JOIN для наборов данных с большим количеством узлов, чем в простом примере. С этой оговоркой, вот мнение:

CREATE VIEW NavigationHierarchy AS
WITH
  hierarchy AS (
    SELECT
      NavId AS RootId
    , 1 AS Depth
    , NavId
    , RIGHT('0000' + CAST(OrderId AS NVARCHAR(MAX)), 4) AS Path
    , ParentId
    , NavName
    FROM NavigationData
    WHERE ParentId = 0
    UNION ALL
    SELECT
      parent.RootId
    , parent.Depth + 1 AS Depth
    , child.NavId
    , parent.Path + '.'
      + RIGHT('0000' + CAST(child.OrderId AS NVARCHAR(MAX)), 4) AS Path
    , child.ParentId
    , child.NavName
    FROM hierarchy AS parent
    INNER JOIN NavigationData AS child
      ON child.ParentId = parent.NavId
  )
SELECT
  desired.NavId AS DesiredNavId
, related.*
FROM hierarchy AS desired
INNER JOIN hierarchy AS related
  ON related.Depth <= desired.Depth + 1
  AND related.RootId IN (desired.RootId, related.RootId)

Большая часть запроса представляет собой прямое рекурсивное снижение иерархии с использованием общего табличного выражения. Суть решения - создание столбцов Path. Естественно, вы можете предпочесть запекать этот запрос непосредственно в более крупный запрос или хранимый процесс, а не создавать представление. Однако представление удобно для тестирования.

Вооружившись видом, теперь мы можем генерировать желаемые результаты в запрошенном порядке. Я включил сгенерированный путь в результат запроса для наглядности. Вот запрос для узла 2:

SELECT NavName, Path
FROM NavigationHierarchy
WHERE DesiredNavId = 2
ORDER BY Path

получают:

Home              0001
About             0002
About Us Page     0002.0001
About Us Page 2   0002.0002
Contact Us        0003

и для узла 6:

SELECT NavName, Path
FROM NavigationHierarchy
WHERE DesiredNavId = 6
ORDER BY Path

получают:

Home              0001
About             0002
About Us Page     0002.0001
Another SubPage   0002.0001.0001
About Us Page 2   0002.0002
Contact Us        0003
0 голосов
/ 18 декабря 2010

Я бы соблазнился создать переменную таблицы, выполнить запрос для NavID <= родительский параметр, где parentID = 0, и добавить в таблицу. Затем получите дочерние элементы, вставьте в табличную переменную и, наконец, получите> NavID Where ParentID = 0.

помадка, но она должна работать.

DECLARE @Table TABLE (NavName nvarchar(50), OrderID int)
INSERT @Table (NavName, OrderID) (SELECT NavName, OrderID FROM @Table1 WHERE (ParentID = 0) AND (NavID <= @ParentID))
INSERT @Table (NavName,OrderID) (SELECT NavName, OrderID+ 500 FROM @Table1 WHERE ParentID = @ParentID )
INSERT @Table (NavName,OrderID) (SELECT NavName, OrderID + 1000 FROM @Table1 WHERE ParentID = 0 AND NavID > @ParentID)

SELECT * FROM @Table ORDER BY OrderID
0 голосов
/ 18 декабря 2010

Общие табличные выражения разрешают рекурсивные запросы. Однако они могут быть очень медленными и могут серьезно запутать оптимизатор запросов, особенно если вы используете (и вам, как правило, следует) параметризованные запросы. Для небольших таблиц (таких как навигация), которые не будут участвовать в больших объединениях с другими (большими) таблицами, CTE работает нормально. Вы также можете кэшировать результат общего выражения таблицы в таблице и перезапускать кеширующий запрос каждый раз, когда меняется навигационная таблица (как правило, не часто, я предполагаю) - оптимизатор запросов гораздо лучше справляется с простыми таблицами отношений «многие ко многим» .

Однако есть и другие способы представления дерева на сервере SQL. Вы можете посмотреть на

  • столбцы стиля lft / rgt , где порядок определяется как порядок столбца lft, а узел X является потомком Y всякий раз, когда Y.lft http://articles.sitepoint.com/article/hierarchical-data-database/2
  • hierarchyid` в в . Sql Server 2008 представил специальный тип данных именно для этой цели; однако обновление древовидной структуры не всегда тривиально с ними. Смотри: http://msdn.microsoft.com/en-us/magazine/cc794278.aspx

Наконец, когда мне нужно использовать CTE, я обычно начинаю с чего-то вроде ...

with nav_tree (ParentID, ChildID, depth_delta) as ( 
    SELECT basenode.NavID, basenode.NavID, 0
    FROM NavTable AS basenode
  UNION ALL
    SELECT treenode.ParentID, basenode.NavID, depth_delta+1
    FROM NavTable AS basenode 
    JOIN nav_tree AS treenode ON basenode.ParentID=treenode.ChildID
)
--select statement here joining the nav_tree with the original table and whatnot

Обратите внимание, что ваши требования к заказу довольно сложны; в частности, если вы хотите, чтобы дочерние элементы элемента были немедленно перечислены в строке; то есть сегмент «О нас», «Другая страница», «О нас страница 2» вашего второго примера. В частности, это означает, что вы не можете просто упорядочить по depth_delta и вторично по orderid - вам понадобится сортировка на основе пути. Возможно, вы захотите сделать это в коде, а не в sql, но вы можете построить путь в CTE следующим образом:

with nav_tree (ParentID, ChildID, depth_delta,orderpath) as ( 
    SELECT basenode.NavID, basenode.NavID, 0, convert(varchar(MAX), basenode.OrderID)
    FROM NavTable AS basenode
  UNION ALL
    SELECT treenode.ParentID, basenode.NavID, depth_delta+1, treenode.orderpath+','+basenode.OrderID
    FROM NavTable AS basenode 
    JOIN nav_tree AS treenode ON basenode.ParentID=treenode.ChildID
)

... и тогда вы можете order by этот путь заказа. Поскольку вы также хотите, чтобы «один уровень ниже» раскрывался у каждого предка или самого себя, вам все равно нужно присоединиться к NavTable, чтобы получить их.

Однако, учитывая ваши требования к сортировке, Я бы порекомендовал идентификаторы HierarchyID : в них встроена семантика сортировки, и они также избегают потенциальных проблем производительности, которые могут представлять CTE.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...