2008-10-27 6 views
24

Disons que je le tableau suivant:Comment créer une requête récursive dans 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 

Je veux récupérer dans une requête tous les descendants de James (Jenna, Jennifer, Peter, Alice, Steve). Merci, Pablo.

+0

Dans quel SGBDR la solution devrait fonctionner? Si c'est Oracle, alors connectez-vous sur CONNECT BY PRIOR – Salamander2007

+0

désolé, oublié de mentionner, dans MSSQL 2005 –

Répondre

34

Sur SQL Server 2005, vous pouvez utiliser 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

Si vous ne voulez pas utiliser la récursivité ou si vous avez besoin d'obtenir un parent d'un enfant, vous pouvez implémenter la solution à partir de l'article de Rob Volk sur sqlteam: http://www.sqlteam.com/article/more-trees-hierarchies- en-sql – nzic

0

Vous ne pouvez pas effectuer de récursivité dans SQL sans procédures stockées. La façon de résoudre cela est d'utiliser des ensembles imbriqués, ils modélisent fondamentalement un arbre dans SQL comme un ensemble.

Notez que cela nécessitera une modification du modèle de données en cours ou, éventuellement, la création d'une vue sur le modèle d'origine.

exemple Postgresql (en utilisant très peu extensions postgresql, juste SERIAL et ON COMMIT DROP, la plupart des SGBDR auront des fonctionnalités similaires):

Configuration:

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); 

Ajout d'un enfant:

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; 

Afficher une piste de bas en haut:

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; 

Afficher l'arbre entier:

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; 

Sélectionnez tout vers le bas d'un certain élément de l'arbre:

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

Mais vous pouvez, voir la réponse de mathieu. –

+0

Le code que j'ai posté utilise une technique applicable à n'importe quelle base de données ANSI-SQL parce que l'OP a oublié de mentionner le SGBDR qu'il utilisait dans son message d'origine (voir les commentaires pour ce post). –

-9

À moins Il me manque quelque chose, la récursivité n'est pas nécessaire ...

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

vous n'obtiendrez pas Jennifer, Peter, Alice, Steve de cette façon – mathieu

3

la réponse de l'utilisation de bas en haut mathieu avec une petite modification:



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 


Questions connexes