Wie erstelle ich eine rekursive Abfrage in MSSQL 2005?
-
04-07-2019 - |
Frage
Lassen Sie uns sagen, ich habe die folgende Tabelle:
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
Ich möchte in einer Abfrage alle Nachkommen von James (Jenna, Jennifer, Peter, Alice, Steve) abgerufen werden. Vielen Dank, Pablo.
Lösung
Auf SQL Server 2005 können Sie verwenden CTE (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
Andere Tipps
Für Boden Verwendung mathieu Antwort mit einer kleinen Modifikation auf:
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
Sie können nicht ohne gespeicherte Prozeduren Rekursion in SQL tun. Die Art und Weise, dies zu lösen, ist Nested Sets verwenden, sie im Grunde auf einen Baum in SQL als Set modelliert.
Beachten Sie, dass dies eine Änderung des aktuellen Datenmodell erfordert oder möglicherweise herauszufinden, wie ein Blick auf das ursprüngliche Modell erstellen.
Postgresql Beispiel (sehr wenige postgresql Erweiterungen verwenden, nur SERIEN und ON COMMIT DROP, haben die meisten RDBMSes ähnliche Funktionalität):
Setup:
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);
Das Hinzufügen eines Kindes:
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;
Zeigen Sie eine Spur von unten nach oben:
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;
den gesamten Baum anzeigen:
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;
Wählen Sie alles nach unten von einem bestimmten Element des Baums:
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;
Es sei denn, ich etwas fehle, Rekursion ist nicht notwendig ...
SELECT d.NAME FROM Customers As d
INNER JOIN Customers As p ON p.CustomerID = d.ParentID
WHERE p.Name = 'James'