2008-10-27 2 views
24

Скажем, у меня есть следующая таблица:Как создать рекурсивный запрос в MSSQL 2005?

CustomerID ParentID Name 
========== ======== ==== 
1   null  John 
2   1  James 
3   2  Jenna 
4   3  Jennifer 
5   3  Peter 
6   5  Alice 
7   5  Steve 
8   1  Larry 

Я хочу, чтобы получить в одном запросе всех потомков Иакова (Jenna, Дженнифер, Питер, Алиса, Стив). Спасибо, Пабло.

+0

В Что RDBMS решение должно работать? Если это Oracle, то узнайте о CONNECT BY PRIOR – Salamander2007 2008-10-27 07:49:39

+0

извините, забыли упомянуть, в MSSQL 2005 – 2008-10-27 07:50:44

ответ

34

В SQL Server 2005 можно использовать CTEs (Common Table Expressions):

with Hierachy(CustomerID, ParentID, Name, Level) 
as 
(
select CustomerID, ParentID, Name, 0 as Level 
    from Customers c 
    where c.CustomerID = 2 -- insert parameter here 
    union all 
    select c.CustomerID, c.ParentID, c.Name, ch.Level + 1 
    from Customers c 
    inner join Hierachy ch 
    on c.ParentId = ch.CustomerID 
) 
select CustomerID, ParentID, Name 
from Hierachy 
where Level > 0 
0

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

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

Postgresql пример (с использованием очень мало расширений PostGreSQL, просто SERIAL и ON COMMIT DROP, большинство RDBMSes будет иметь аналогичные функциональные возможности):

Установка:

CREATE TABLE objects(
    id SERIAL PRIMARY KEY, 
    name TEXT, 
    lft INT, 
    rgt INT 
); 

INSERT INTO objects(name, lft, rgt) VALUES('The root of the tree', 1, 2); 

Добавление ребенка:

START TRANSACTION; 

-- postgresql doesn't support variables so we create a temporary table that 
-- gets deleted after the transaction has finished. 

CREATE TEMP TABLE left_tmp(
    lft INT 
) ON COMMIT DROP; -- not standard sql 

-- store the left of the parent for later use 
INSERT INTO left_tmp (lft) VALUES((SELECT lft FROM objects WHERE name = 'The parent of the newly inserted node')); 

-- move all the children already in the set to the right 
-- to make room for the new child 
UPDATE objects SET rgt = rgt + 2 WHERE rgt > (SELECT lft FROM left_tmp LIMIT 1); 
UPDATE objects SET lft = lft + 2 WHERE lft > (SELECT lft FROM left_tmp LIMIT 1); 

-- insert the new child 
INSERT INTO objects(name, lft, rgt) VALUES(
    'The name of the newly inserted node', 
    (SELECT lft + 1 FROM left_tmp LIMIT 1), 
    (SELECT lft + 2 FROM left_tmp LIMIT 1) 
); 

COMMIT; 

Показать след внизу:

SELECT 
    parent.id, parent.lft 
FROM 
    objects AS current_node 
INNER JOIN 
    objects AS parent 
ON 
    current_node.lft BETWEEN parent.lft AND parent.rgt 
WHERE 
    current_node.name = 'The name of the deepest child' 
ORDER BY 
    parent.lft; 

Показать все дерево:

SELECT 
    REPEAT(' ', CAST((COUNT(parent.id) - 1) AS INT)) || '- ' || current_node.name AS indented_name 
FROM 
    objects current_node 
INNER JOIN 
    objects parent 
ON 
    current_node.lft BETWEEN parent.lft AND parent.rgt 
GROUP BY 
    current_node.name, 
    current_node.lft 
ORDER BY 
    current_node.lft; 

Выберите все вниз из определенного элемента дерева:

SELECT 
    current_node.name AS node_name 
FROM 
    objects current_node 
INNER JOIN 
    objects parent 
ON 
    current_node.lft BETWEEN parent.lft AND parent.rgt 
AND 
    parent.name = 'child' 
GROUP BY 
    current_node.name, 
    current_node.lft 
ORDER BY 
    current_node.lft; 
+2

Но вы можете, см. Ответ Матье. – 2008-10-27 08:39:16

+0

Введенный мной код использует технику, применимую к любой базе данных ANSI-SQL, потому что OP забыл упомянуть СУБД, которые он использовал в своем первоначальном сообщении (см. Комментарии к этому сообщению). – 2008-10-27 09:02:39

-9

Если только Мне что-то не хватает, рекурсия не нужна ...

SELECT d.NAME FROM Customers As d 
INNER JOIN Customers As p ON p.CustomerID = d.ParentID 
WHERE p.Name = 'James' 
3

Для ответа снизу вверх использовать Матье с небольшой модификацией:



with Hierachy(CustomerID, ParentID, Name, Level) 
as 
(
select CustomerID, ParentID, Name, 0 as Level 
    from Customers c 
    where c.CustomerID = 2 -- insert parameter here 
    union all 
    select c.CustomerID, c.ParentID, c.Name, ch.Level + 1 
    from Customers c 
    inner join Hierachy ch 

    -- EDITED HERE -- 
    on ch.ParentId = c.CustomerID 
    ----------------- 

) 
select CustomerID, ParentID, Name 
from Hierachy 
where Level > 0 


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