Таблица SQL с родительскими и дочерними узлами (таблица навигации) - PullRequest
1 голос
/ 17 ноября 2009

У меня есть одна таблица с родительскими и дочерними узлами, и у каждого есть номер заказа.

Я пытаюсь написать один запрос для вывода их по порядку, для списка навигации с категориями и подкатегориями.

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

Таблица:

DBID | Title | ParentID | OrderNum
 1      aaa       0          1
 2      bbb       0          2
 3      ccc       1          1
 4      ddd       1          2
 5      eee       2          1

и я хочу получить результирующий набор как:

DBID | Title | ParentID | OrderNum
 1      aaa       0          1      <<< main 
 3      ccc       1          1      <<< child
 4      ddd       1          2      <<< 2nd child
 2      bbb       0          2      <<< main 
 5      eee       2          1      <<< child

Я изучал использование рекурсивного выбора SQL или выражений общих таблиц (CTE), но пока не смог выяснить это.

Может ли кто-нибудь помочь направить меня в правильном направлении?

(Использование SQL Server 2005 / ASP.Net C #)

Edit: Я должен уточнить, что мне нужно только перейти на 2 уровня - то есть родитель и потомок, поэтому не нужно повторять проклятье бесконечно, поэтому я предполагаю, что ответ гораздо проще, чем я ожидал.

Ответы [ 6 ]

2 голосов
/ 17 ноября 2009

Нужна ли рекурсия? Похоже, это дает правильный результат:

declare @t table 
(DBID           int
,Title          varchar(10)
,ParentID       int
,OrderNum       int
)

insert @t
      select 1,'aaa',0,1
union select 2,'bbb',0,2
union select 3,'ccc',1,1
union select 4,'ddd',1,2
union select 5,'eee',2,1

select * 
from @t
order by ISNULL(NULLIF(ParentID,0),DBID)
        ,ParentID
        ,OrderNum
        ,DBID

EDIT

Объяснение:

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

Единственная сложная часть этого состоит в том, что родительские строки (с ParentID = 0) неявно получают членство в своей группе из DBID, тогда как все остальные строки получают его из ParentID.

Первое WHERE условие (ISNULL(NULLIF(ParentID,0),DBID)) обрабатывает это. Это также может быть записано как

CASE WHEN ParentID = 0
     THEN DBID
     ELSE ParentID
END

Я использовал NULLIF...ISNULL, поскольку он более лаконичен, но если бы условия сортировки были более сложными, я бы использовал CASE...WHEN.

1 голос
/ 17 ноября 2009

Пару лет назад я обнаружил, что самый простой способ сделать это (после охоты) - добавить столбцы «глубина» и «путь» в таблицу:

DBID | Title | ParentID | OrderNum | Path  | Depth
 0      Root     null        1       /         0
 1      aaa       0          1       /1/       1
 2      bbb       0          2       /2/       1
 3      ccc       1          1       /1/3/     2
 4      ddd       1          2       /1/4/     2
 5      eee       2          1       /2/5/     2

У меня есть пара триггеров в таблице для автоматического обновления при вставке и обновлении:

Вставить триггер

ALTER TRIGGER [dbo].[SiteMap_InsertTrigger] 
  ON  [dbo].[SiteMap] 
  AFTER INSERT
AS 
BEGIN
  -- SET NOCOUNT ON added to prevent extra result sets from
  -- interfering with SELECT statements.
  SET NOCOUNT ON;

  UPDATE child
    -- set the depth of this "child" to be the
    -- depth of the parent, plus one.
    SET Depth = ISNULL(parent.depth + 1, 0),
      -- the lineage is simply the lineage of the parent,
      -- plus the child's ID (and appropriate '/' characters
      Path = ISNULL(parent.Path, '/') + LTrim(child.DBID) + '/'

    -- we can't update the "inserted" table directly,
    -- so we find the corresponding child in the
    -- "real" table
    FROM SiteMap child INNER JOIN inserted i ON i.Id = child.DBID
      -- now, we attempt to find the parent of this
      -- "child" - but it might not exist, so these
      -- values may well be NULL
      LEFT OUTER JOIN SiteMap parent ON child.ParentId = parent.DBID
END

Триггер обновления

ALTER TRIGGER [dbo].[SiteMap_UpdateTrigger] 
  ON  [dbo].[SiteMap] 
  AFTER UPDATE
AS 
BEGIN
  -- SET NOCOUNT ON added to prevent extra result sets from
  -- interfering with SELECT statements.
  SET NOCOUNT ON;

  -- if we've modified the parentId, then we
  -- need to do some calculations
  IF UPDATE (ParentId)
  BEGIN
    UPDATE child
      /*
      to calculate the correct depth of a node, remember that
        - old.depth is the depth of its old parent
        - child.depth is the original depth of the node
          we're looking at before a parent node moved.
          note that this is not necessarily old.depth + 1,
          as we are looking at all depths below the modified
          node
      The depth of the node relative to the old parent is
      (child.depth - old.depth), then we simply add this to the
      depth of the new parent, plus one.
      */
      SET Depth = child.Depth - old.Depth + ISNULL(parent.Depth + 1,0),
        Path = ISNULL(parent.Path,'/') + LTrim(old.DBID) + '/' +
        right(child.Path, len(child.Path) - len(old.Path))
        -- if the parentId has been changed for some row
        -- in the "inserted" table, we need to update the
        -- fields in all children of that node, and the
        -- node itself                 
      FROM SiteMap child
      INNER JOIN inserted old ON child.Path LIKE old.Path + '%'
        -- as with the insert trigger, attempt to find the parent
        -- of the updated row
      LEFT OUTER JOIN SiteMap parent ON old.ParentId=parent.DBID
  END;
END;

Что значительно упрощает переход в определенную категорию и очень быстрое извлечение всех детей и дочерних элементов путем поиска по пути или глубине.

Интересно, что я только что обнаружил, что SQL Server 2008 включает новый тип: HierarchyId , который в основном сворачивает столбец Path в компактный тип.

0 голосов
/ 24 ноября 2010

Принятый ответ на месте. Вот и в linq:

var orderedItems = from item in dataContext.items
                   orderby (item.ParentID == 0 ? item.DBID : item.ParentID),
                       item.ParentID , item.OrderNum, item.DBID
                   select item;
0 голосов
/ 17 ноября 2009

Это рекурсивный подход. используя CTE.

with cte
as
(
select *, 1 as [level], [DBID] as [father]
from @t
where parentID=0
union all
select t.*, [level]+1, cte.[father]
from @t t
inner join cte on cte.DBID=t.parentID
)
select  [DBID],Title, ParentID,  OrderNum  
from cte
order by [father], [level],  OrderNum  

0 голосов
/ 17 ноября 2009

Вы можете использовать модель с вложенным множеством . Это позволяет извлекать целые деревья в одном операторе SQL. Недостатком является то, что операции записи становятся более сложными.

В качестве альтернативы вы можете написать рекурсивную хранимую процедуру.

0 голосов
/ 17 ноября 2009

Да, вы можете достичь этого с помощью рекурсивного sql или cte.

Я думаю, что это решение поможет вам решить эту иерархическую таблицу выбора.

http://geekswithblogs.net/dotNETPlayground/archive/2007/12/28/118017.aspx

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