Dit is een klassiek gebruik van een eenvoudige recursieve algemene tabeluitdrukking (WITH RECURSIVE
), beschikbaar in PostgreSQL 8.4 en later.
Hier gedemonstreerd:http://sqlfiddle.com/#!12/78e15/9
Gezien de voorbeeldgegevens als SQL:
CREATE TABLE Table1
("ID1" text, "ID2" text)
;
INSERT INTO Table1
("ID1", "ID2")
VALUES
('vc1', 'vc2'),
('vc2', 'vc3'),
('vc3', 'vc4'),
('vc4', 'rc7')
;
Je zou kunnen schrijven:
WITH RECURSIVE chain(from_id, to_id) AS (
SELECT NULL, 'vc2'
UNION
SELECT c.to_id, t."ID2"
FROM chain c
LEFT OUTER JOIN Table1 t ON (t."ID1" = to_id)
WHERE c.to_id IS NOT NULL
)
SELECT from_id FROM chain WHERE to_id IS NULL;
Wat dit doet, is iteratief door de keten lopen, waarbij elke rij wordt toegevoegd aan de chain
tabel als van- en naar-pointers. Wanneer het een rij tegenkomt waarvoor de verwijzing 'naar' niet bestaat, zal het een null 'naar' verwijzing voor die rij toevoegen. De volgende iteratie zal opmerken dat de verwijzing 'naar' null is en nul rijen produceert, waardoor de iteratie stopt.
De buitenste query haalt dan rijen op waarvan is vastgesteld dat ze het einde van de keten zijn door een niet-bestaande to_id te hebben.
Het kost wat moeite om recursieve CTE's te omzeilen. De belangrijkste dingen om te begrijpen zijn:
-
Ze beginnen met de uitvoer van een eerste query, die ze herhaaldelijk verenigen met de uitvoer van het "recursieve deel" (de query na de
UNION
ofUNION ALL
) totdat het recursieve deel geen rijen toevoegt. Dat stopt iteratie. -
Ze zijn niet echt recursief, meer iteratief, hoewel ze goed zijn voor het soort dingen waarvoor je recursie zou kunnen gebruiken.
Je bouwt dus eigenlijk een tabel in een lus. U kunt geen rijen verwijderen of wijzigen, alleen nieuwe toevoegen, dus u hebt over het algemeen een buitenste query nodig die de resultaten filtert om de gewenste resultaatrijen te krijgen. U voegt vaak extra kolommen toe met tussentijdse gegevens die u gebruikt om de status van de iteratie bij te houden, stopcondities te controleren, enz.
Het kan helpen om naar het ongefilterde resultaat te kijken. Als ik de laatste samenvattingsquery vervang door een eenvoudige SELECT * FROM chain
Ik kan de tabel zien die is gegenereerd:
from_id | to_id
---------+-------
| vc2
vc2 | vc3
vc3 | vc4
vc4 | rc7
rc7 |
(5 rows)
De eerste rij is de handmatig toegevoegde startpuntrij, waar je specificeert wat je wilt opzoeken - in dit geval was dat vc2
. Elke volgende rij is toegevoegd door de UNION
ed recursieve term, die een LEFT OUTER JOIN
. doet op het vorige resultaat en retourneert een nieuwe set rijen die de vorige to_id
. koppelen (nu in de from_id
kolom) naar de volgende to_id
. Als de LEFT OUTER JOIN
komt niet overeen dan de to_id
zal null zijn, waardoor de volgende aanroep nu rijen retourneert en de iteratie beëindigt.
Omdat deze zoekopdracht niet probeert om alleen de laatste . toe te voegen rij elke keer, het is eigenlijk elke iteratie een behoorlijk beetje werk aan het herhalen. Om dat te voorkomen, zou je een benadering moeten gebruiken die meer lijkt op die van Gordon, maar bovendien moet je filteren op het vorige diepteveld wanneer je de invoertabel scant, zodat je alleen de meest recente rij hebt toegevoegd. In de praktijk is dit meestal niet nodig, maar het kan een probleem zijn voor zeer grote datasets of waar u geen geschikte indexen kunt maken.
U kunt meer leren in de PostgreSQL-documentatie over CTE's.