2013-08-07 2 views
27

Мне нужно получить упорядоченную иерархию дерева определенным образом. В таблице вопрос выглядит немного как это (все поля ID являются uniqueidentifiers, я упростил данные ради примера):CTE Recursion для получения иерархии деревьев

EstimateItemID EstimateID ParentEstimateItemID  ItemType 
-------------- ---------- --------------------  -------- 
     1    A    NULL    product 
     2    A     1    product 
     3    A     2    service 
     4    A    NULL    product 
     5    A     4    product 
     6    A     5    service 
     7    A     1    service 
     8    A     4    product

Графическое представление структуры дерева (* «сервис» обозначает):

 
      A 
     ___/ \___ 
    /  \ 
    1   4 
/\  /\ 
    2 7*  5 8 
/  /
3*   6* 

Используя этот запрос, я могу получить иерархию (просто делать вид «A» является UniqueIdentifier, я знаю, что это не в реальной жизни):

DECLARE @EstimateID uniqueidentifier 
SELECT @EstimateID = 'A' 

;WITH temp as(
    SELECT * FROM EstimateItem 
    WHERE EstimateID = @EstimateID 

    UNION ALL 

    SELECT ei.* FROM EstimateItem ei 
    INNER JOIN temp x ON ei.ParentEstimateItemID = x.EstimateItemID 
) 

SELECT * FROM temp 

Это дает мне ребенок EstimateID 'A', но в что он появляется в таблице. то есть:

EstimateItemID 
-------------- 
     1 
     2 
     3 
     4 
     5 
     6 
     7 
     8

К сожалению, то, что мне нужно, это упорядоченная иерархия с результирующим набором, который следует следующим ограничениям:

 
1. each branch must be grouped 
2. records with ItemType 'product' and parent are the top node 
3. records with ItemType 'product' and non-NULL parent grouped after top node 
4. records with ItemType 'service' are bottom node of a branch 

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

EstimateItemID 
-------------- 
     1 
     2 
     3 
     7 
     4 
     5 
     8 
     6 

Что нужно добавить к моему запросу для этого?

ответ

53

Попробуйте это:

;WITH items AS (
    SELECT EstimateItemID, ItemType 
    , 0 AS Level 
    , CAST(EstimateItemID AS VARCHAR(255)) AS Path 
    FROM EstimateItem 
    WHERE ParentEstimateItemID IS NULL AND EstimateID = @EstimateID 

    UNION ALL 

    SELECT i.EstimateItemID, i.ItemType 
    , Level + 1 
    , CAST(Path + '.' + CAST(i.EstimateItemID AS VARCHAR(255)) AS VARCHAR(255)) 
    FROM EstimateItem i 
    INNER JOIN items itms ON itms.EstimateItemID = i.ParentEstimateItemID 
) 

SELECT * FROM items ORDER BY Path 

С Path - рядами отсортированных родителями узлов

Если вы хотите отсортировать ChildNodes от ItemType для каждого уровня, чем вы можете играть с Level и SUBSTRING из Path колонны. ...

Здесь SQLFiddle с образцом данных

+0

Блестящий. Это уже несколько лет, но сегодня это полезно. Однако, простите за высказывание, я нашел пример, представленный в оригинальном посте, для меня перевести на более распространенное решение. Итак, я перепробовал вашу (большую) идею, используя более распространенные данные, имя таблицы и поля, чтобы сделать ее проще для других. – ptownbro

+0

Есть ли способ сделать заказ по ItemType с уровнем 0, а иерархия должна оставаться такой, какая есть? –

0

Я считаю, что вам нужно добавить следующие строки в результатах вашего КТР ...

  1. BranchID = какой-то идентификатор, который однозначно идентифицирует ветвь. Простите меня за то, что вы не более конкретны, но я не уверен, что определяет отрасль для ваших нужд. В вашем примере показано двоичное дерево, в котором все ветви возвращаются к корню.
  2. ItemTypeID где (например) 0 = Продукт и 1 = услуга.
  3. Родитель = идентифицирует родителя.

Если они существуют на выходе, я думаю, вы должны иметь возможность использовать вывод из своего запроса как другой CTE или как предложение FROM в запросе. Порядок по BranchID, ItemTypeID, Parent.

+0

Корень филиала будет идентифицированной записи с NULL ParentEstimateItemID. Итак, все под «1» будет ветвью x, а все под 4 - ветвью y. Я не невероятно умею с sql, и изучаю CTE на лету, так что простите меня. Нужно ли добавлять ваши баллы в первый оператор SELECT? – Woods8460

3

Это дополнение к замечательной идее Фабио сверху. Как я сказал в своем ответе на его оригинальный пост. Я повторно опубликовал его идею, используя более общие данные, имя таблицы и поля, чтобы упростить другие действия.

Спасибо, Фабио! Великое имя, кстати.

Первые некоторые данные для работы с:

CREATE TABLE tblLocations (ID INT IDENTITY(1,1), Code VARCHAR(1), ParentID INT, Name VARCHAR(20)); 

INSERT INTO tblLocations (Code, ParentID, Name) VALUES 
('A', NULL, 'West'), 
('A', 1, 'WA'), 
('A', 2, 'Seattle'), 
('A', NULL, 'East'), 
('A', 4, 'NY'), 
('A', 5, 'New York'), 
('A', 1, 'NV'), 
('A', 7, 'Las Vegas'), 
('A', 2, 'Vancouver'), 
('A', 4, 'FL'), 
('A', 5, 'Buffalo'), 
('A', 1, 'CA'), 
('A', 10, 'Miami'), 
('A', 12, 'Los Angeles'), 
('A', 7, 'Reno'), 
('A', 12, 'San Francisco'), 
('A', 10, 'Orlando'), 
('A', 12, 'Sacramento'); 

Теперь рекурсивный запрос:

-- Note: The 'Code' field isn't used, but you could add it to display more info. 
;WITH MyCTE AS (
    SELECT ID, Name, 0 AS TreeLevel, CAST(ID AS VARCHAR(255)) AS TreePath 
    FROM tblLocations T1 
    WHERE ParentID IS NULL 

    UNION ALL 

    SELECT T2.ID, T2.Name, TreeLevel + 1, CAST(TreePath + '.' + CAST(T2.ID AS VARCHAR(255)) AS VARCHAR(255)) AS TreePath 
    FROM tblLocations T2 
    INNER JOIN MyCTE itms ON itms.ID = T2.ParentID 
) 
-- Note: The 'replicate' function is not needed. Added it to give a visual of the results. 
SELECT ID, Replicate('.', TreeLevel * 4)+Name 'Name', TreeLevel, TreePath 
FROM MyCTE 
ORDER BY TreePath; 
+0

хорошее решение! Я разместил на SQLFiddle, http://sqlfiddle.com/#!6/5e70c/2/0 – MAbraham1

Смежные вопросы