Detta är en klassisk användning av ett enkelt rekursivt gemensamt tabelluttryck (WITH RECURSIVE
), tillgänglig i PostgreSQL 8.4 och senare.
Demonstreras här:http://sqlfiddle.com/#!12/78e15/9
Givet exempeldata som SQL:
CREATE TABLE Table1
("ID1" text, "ID2" text)
;
INSERT INTO Table1
("ID1", "ID2")
VALUES
('vc1', 'vc2'),
('vc2', 'vc3'),
('vc3', 'vc4'),
('vc4', 'rc7')
;
Du kan skriva:
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;
Vad detta gör är att upprepa kedjan, lägga till varje rad i chain
tabell som från- och till-pekare. När den stöter på en rad för vilken "till"-referensen inte finns kommer den att lägga till en noll "till"-referens för den raden. Nästa iteration kommer att märka att "till"-referensen är noll och producerar nollrader, vilket gör att iterationen slutar.
Den yttre frågan plockar sedan upp rader som har bestämts vara slutet av kedjan genom att ha ett icke-existerande to_id.
Det krävs lite ansträngning för att komma runt rekursiva CTE:er. De viktigaste sakerna att förstå är:
-
De börjar med utdata från en initial fråga, som de upprepade gånger förenar med utdata från den "rekursiva delen" (frågan efter
UNION
ellerUNION ALL
) tills den rekursiva delen inte lägger till några rader. Det stoppar iteration. -
De är inte riktigt rekursiva, mer iterativa, även om de är bra för den sortens saker du kan använda rekursion till.
Så du bygger i princip ett bord i en slinga. Du kan inte ta bort rader eller ändra dem, bara lägga till nya, så du behöver vanligtvis en yttre fråga som filtrerar resultaten för att få de resultatrader du vill ha. Du lägger ofta till extra kolumner som innehåller mellanliggande data som du använder för att spåra tillståndet för iterationen, kontrollera stoppvillkor etc.
Det kan hjälpa att titta på det ofiltrerade resultatet. Om jag ersätter den slutliga sammanfattningsfrågan med en enkel SELECT * FROM chain
Jag kan se tabellen som har genererats:
from_id | to_id
---------+-------
| vc2
vc2 | vc3
vc3 | vc4
vc4 | rc7
rc7 |
(5 rows)
Den första raden är den manuellt tillagda startpunktsraden, där du anger vad du vill slå upp - i det här fallet var det vc2
. Varje efterföljande rad lades till av UNION
ed rekursiv term, som gör en LEFT OUTER JOIN
på det föregående resultatet och returnerar en ny uppsättning rader som parar ihop föregående to_id
(nu i from_id
kolumn) till nästa to_id
. Om LEFT OUTER JOIN
matchar inte då to_id
kommer att vara null, vilket gör att nästa anrop returnerar nu-rader och avslutar iterationen.
Eftersom den här frågan inte försöker lägga till bara den sista rad varje gång, det är faktiskt att upprepa en hel del arbete varje iteration. För att undvika det skulle du behöva använda ett tillvägagångssätt mer likt Gordons, men dessutom filtrera på föregående djupfält när du skannade inmatningstabellen, så att du bara gick med i den senaste raden. I praktiken är detta vanligtvis inte nödvändigt, men det kan vara ett problem för mycket stora datamängder eller där du inte kan skapa lämpliga index.
Mer kan du lära dig i PostgreSQL-dokumentationen om CTE.