Това е класическо използване на прост рекурсивен израз на обща таблица (WITH RECURSIVE
), наличен в PostgreSQL 8.4 и по-нови версии.
Демонстрирано тук:http://sqlfiddle.com/#!12/78e15/9
Като се имат предвид примерните данни като SQL:
CREATE TABLE Table1
("ID1" text, "ID2" text)
;
INSERT INTO Table1
("ID1", "ID2")
VALUES
('vc1', 'vc2'),
('vc2', 'vc3'),
('vc3', 'vc4'),
('vc4', 'rc7')
;
Можете да напишете:
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;
Това, което прави, е итеративно да върви по веригата, като добавя всеки ред към chain
таблица като указатели от и до. Когато срещне ред, за който препратката „до“ не съществува, тя ще добави нулева препратка „до“ за този ред. Следващата итерация ще забележи, че препратката „до“ е нула и ще произведе нула редове, което води до края на итерацията.
След това външната заявка взема редове, за които е определено, че са края на веригата, като има несъществуващ to_id.
Необходими са малко усилия, за да се запознаете с рекурсивните CTE. Те ключовите неща, които трябва да разберете, са:
-
Те започват с изхода на първоначална заявка, която многократно обединяват с изхода на "рекурсивната част" (заявката след
UNION
илиUNION ALL
), докато рекурсивната част не добавя редове. Това спира повторението. -
Те всъщност не са рекурсивни, по-итеративни, въпреки че са добри за неща, за които може да използвате рекурсия.
Така че по същество изграждате таблица в цикъл. Не можете да изтривате редове или да ги променяте, а само да добавяте нови, така че обикновено имате нужда от външна заявка, която филтрира резултатите, за да получите желаните от вас редове с резултати. Често ще добавяте допълнителни колони, съдържащи междинни данни, които използвате за проследяване на състоянието на итерацията, контрол на условията за спиране и т.н.
Може да помогне да погледнете нефилтрирания резултат. Ако заменя окончателната обобщена заявка с проста SELECT * FROM chain
Виждам таблицата, която е генерирана:
from_id | to_id
---------+-------
| vc2
vc2 | vc3
vc3 | vc4
vc4 | rc7
rc7 |
(5 rows)
Първият ред е ръчно добавеният ред за начална точка, където посочвате какво искате да търсите - в този случай това е vc2
. Всеки следващ ред беше добавен от UNION
ed рекурсивен термин, който извършва LEFT OUTER JOIN
върху предишния резултат и връща нов набор от редове, които свързват предишния to_id
(сега в from_id
колона) към следващия to_id
. Ако LEFT OUTER JOIN
не съвпада тогава to_id
ще бъде null, което кара следващото извикване да върне редовете сега и да приключи итерацията.
Тъй като тази заявка не се опитва да добави само последната ред всеки път, той всъщност повтаря доста работа на всяка итерация. За да избегнете това, ще трябва да използвате подход, по-скоро подобен на Гордън, но допълнително да филтрирате по предишното поле за дълбочина, когато сте сканирали таблица за въвеждане, така че сте се присъединили само към последния ред. На практика това обикновено не е необходимо, но може да е проблем за много големи набори от данни или когато не можете да създадете подходящи индекси.
Повече можете да научите в документацията на PostgreSQL за CTEs.