Реализация множественного родительского дерева (или орграфа) SQL Server 2005 - PullRequest
2 голосов
/ 13 мая 2009

Мне нужно реализовать многопартийное дерево (или орграф) на SQL Server 2005. Я прочитал несколько статей, но в большинстве из них используются однопартийные деревья с уникальным корнем, подобным следующему.

-My PC
   -Drive C
      -Documents and Settings
      -Program Files
         -Adobe
         -Microsoft
      -Folder X
   -Drive D
      -Folder Y
      -Folder Z

В этом все происходит от корневого элемента (Мой ПК).

В моем случае у ребенка может быть более одного родителя, например:

G  A
 \ /
  B
 / \ 
X   C
  /  \
  D   E
  \ /
   F

Итак, у меня есть следующий код:

create table #ObjectRelations
(
    Id varchar(20),
    NextId varchar(20)
)

insert into #ObjectRelations values ('G', 'B')
insert into #ObjectRelations values ('A', 'B') 
insert into #ObjectRelations values ('B', 'C')
insert into #ObjectRelations values ('B', 'X')
insert into #ObjectRelations values ('C', 'E') 
insert into #ObjectRelations values ('C', 'D') 
insert into #ObjectRelations values ('E', 'F') 
insert into #ObjectRelations values ('D', 'F') 

declare @id varchar(20)
set @id = 'A';

WITH Objects (Id, NextId) AS
( -- This is the 'Anchor' or starting point of the recursive query
  SELECT rel.Id,
         rel.NextId
    FROM #ObjectRelations rel
   WHERE rel.Id = @id
   UNION ALL -- This is the recursive portion of the query
  SELECT rel.Id,
         rel.NextId
    FROM #ObjectRelations rel
   INNER JOIN Objects -- Note the reference to CTE table name (Recursive Join)
      ON rel.Id = Objects.NextId
)
SELECT  o.*
FROM    Objects o

drop table #ObjectRelations

, который возвращает следующий SET:

Id                   NextId
-------------------- --------------------
A                    B
B                    C
B                    X
C                    E
C                    D
D                    F
E                    F

Ожидаемый результат SET:

Id                   NextId
-------------------- --------------------
G                    B
A                    B
B                    C
B                    X
C                    E
C                    D
D                    F
E                    F

Обратите внимание, что отношение G-> B отсутствует, потому что оно запрашивает начальный объект (который также не работает для меня, потому что я не знаю корневой объект с самого начала) и используя A в качестве начала точка будет игнорировать отношения G-> B.

Итак, этот код не работает в моем случае, потому что он запрашивает начальный объект, что очевидно в SINGLE-родительском дереве (всегда будет корневым объектом). Но в многопользовательском дереве у вас может быть более 1 «корневого» объекта (как в примере, G и A - это «корневые» объекты, где root - это объект, у которого нет родителя (предка)).

Так что я застрял здесь ... Мне нужно изменить запрос, чтобы НЕ запрашивать начальный объект, и рекурсивно пройти по всему дереву. Я не знаю, возможно ли это с реализацией (Id, NextId) ... может быть, мне нужно сохранить ее как график, используя какую-то матрицу инцидентности, матрицу смежности или что-то еще (см. http://willets.org/sqlgraphs.html).

Любая помощь? Что вы думаете, ребята? Большое спасибо за потраченное время =)

Ура!

Источники: Источник 1 Источник 2 Источник 3

Ответы [ 3 ]

2 голосов
/ 04 июня 2009

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

create table #ObjectRelations
(
    Id varchar(20),
    NextId varchar(20)
)

/* Cycle */
/*
insert into #ObjectRelations values ('A', 'B')
insert into #ObjectRelations values ('B', 'C') 
insert into #ObjectRelations values ('C', 'A')
*/

/* Multi root */

insert into #ObjectRelations values ('G', 'B')
insert into #ObjectRelations values ('A', 'B') 
insert into #ObjectRelations values ('B', 'C')
insert into #ObjectRelations values ('B', 'X')
insert into #ObjectRelations values ('C', 'E') 
insert into #ObjectRelations values ('C', 'D') 
insert into #ObjectRelations values ('E', 'F') 
insert into #ObjectRelations values ('D', 'F') 


declare @startIds table
(
    Id varchar(20) primary key
)

;WITH 
    Ids (Id) AS
    (
        SELECT  Id
        FROM    #ObjectRelations
    ),
    NextIds (Id) AS
    (
        SELECT  NextId
        FROM    #ObjectRelations
    )
INSERT INTO @startIds
/* This select will not return anything since there are not objects without predecessor, because it's a cyclic of course */
SELECT DISTINCT
    Ids.Id
FROM
    Ids
LEFT JOIN
    NextIds on Ids.Id = NextIds.Id
WHERE
    NextIds.Id IS NULL
UNION
/* So let's just pick anyone. (the way I will be getting the starting object for a cyclic doesn't matter for the regarding problem)*/
SELECT TOP 1 Id FROM Ids

;WITH Objects (Id, NextId, [Level], Way) AS
( -- This is the 'Anchor' or starting point of the recursive query
  SELECT rel.Id,
         rel.NextId,
         1,
         CAST(rel.Id as VARCHAR(MAX))
    FROM #ObjectRelations rel
   WHERE rel.Id IN (SELECT Id FROM @startIds)

   UNION ALL -- This is the recursive portion of the query

  SELECT rel.Id,
         rel.NextId,
         [Level] + 1,
         RecObjects.Way + ', ' + rel.Id
    FROM #ObjectRelations rel
   INNER JOIN Objects RecObjects -- Note the reference to CTE table name (Recursive Join)
      ON rel.Id = RecObjects.NextId
   WHERE RecObjects.Way NOT LIKE '%' + rel.Id + '%'

)
SELECT  DISTINCT 
        Id,
        NextId,
        [Level]
FROM    Objects
ORDER BY [Level]

drop table #ObjectRelations

Может быть кому-нибудь пригодится. Это для меня = P Спасибо

1 голос
/ 13 мая 2009

Если вы хотите использовать все корневые объекты в качестве начальных объектов, вы должны сначала обновить свои данные, чтобы включить информацию о корневых объектах (и листьях). Вы должны добавить следующие вставки:

insert into #ObjectRelations values (NULL, 'G')
insert into #ObjectRelations values (NULL, 'A')
insert into #ObjectRelations values ('X', NULL)
insert into #ObjectRelations values ('F', NULL)

Конечно, вы также можете написать свой якорный запрос таким образом, чтобы вы выбрали в качестве корневых узлов записи, которые имеют Id, а не NextId, но это проще.

Затем измените свой якорный запрос, чтобы он выглядел следующим образом:

SELECT rel.Id,
       rel.NextId
FROM #ObjectRelations rel
WHERE rel.Id IS NULL

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

Это можно исправить, изменив выражение select на следующее (обратите внимание на DISTINCT):

SELECT DISTINCT o.*
FROM   Objects o
0 голосов
/ 05 апреля 2012

Если вы не хотите делать вставки, предложенные Рональдом, это подойдет !.

WITH CTE_MultiParent  (ID, ParentID) 
AS 
(
    SELECT ID, ParentID FROM #ObjectRelations
    WHERE ID NOT IN 
    (
        SELECT DISTINCT ParentID FROM #ObjectRelations
    )
    UNION ALL
    SELECT ObjR.ID, ObjR.ParentID FROM #ObjectRelations ObjR INNER JOIN CTE_MultiParent
    ON CTE_MultiParent.ParentID = ObjR.Id
)

SELECT DISTINCT * FROM CTE_MultiParent
...