Saturday, February 27, 2010 7:30 PM
by
havetta
Rekurzivní hledání ve stromu pomocí SQL CTE
Název tohoto článečku je trošku nejasný, ale osvětlení přijde hned.
Dělám na projektu, kde je v SQL 2008 databázi tabulka obsahující objekt X. Ten může mít klasické hierarchické uspořádání s jiným objektem typu X, čím se vytvoří typický strom. Každý objekt X má jednu vlastnost M, která je daná číselníkem C.
Úkol pro řešení byl na první pohled jednoduchý. Mám jednu instanci X1 objektu typu X, která ale může být v libovolné úrovni stromu. Jak co nejjednodušeji zjistím, které hodnoty číselníku C nejsou použité v žádném objektu typu X, který je ve stejném stromě jak ten X1.
Samozřejmě by se dalo to řešit kódem, ale po krátkém bádání ve schopnostech SQL 2005/8 jsem našel dle mého názoru elegantní řešení, které vyřeší vše v SQL serveru. Využil jsem schpnost rekurzivního prohledávání v SQL bez nutnosti vytvářet temporary table. Odborně to má název CTE, je plno blogů které popisují prohledávání od root elementu směrem dolů (většinou vazbu zaměstnanec - vedoucí)
Já udělal při bádání dvě tabulky, Task se sloupečkem Id, ParentId, Popis a Typ. Pole Typ je id číselníku Type (druhá tabulka). První blok mého SQL kódu hledá root prvek stromu (to jest ten, kde parentId bude NULL) a pak lehce najde vše, co se ve stromě vyskytuje a pomocí NOT IN najde v číselníku jen to, co ve stromě není.
Jen pro zajímavost, zda se ve stromě pohybuji směrem dolů nebo nahoru je dáno pomocí JOIN podmínky v sekcích WITH, podle toho zda z tabulky beru Id nebo ParentId.
DECLARE @root AS INT;
DECLARE @start AS INT;
SET @start = 5;
WITH UpCTE
AS
(
SELECT Id, ParentID, Popis,Typ, 0 as lvl
FROM Task
WHERE Id = @start
UNION ALL
SELECT C.Id, C.ParentID, C.Popis,C.Typ, P.lvl + 1
FROM UpCTE as P
JOIN Task AS C ON P.ParentID = C.Id
)
SELECT @root =Id FROM UpCTE
WHERE ParentID IS NULL;
WITH SubsCTE
AS
(
SELECT Id, ParentID, Popis,Typ, 0 as lvl
FROM Task
WHERE Id = @root
UNION ALL
SELECT C.Id, C.ParentID, C.Popis,C.Typ, P.lvl + 1
FROM SubsCTE as P
JOIN Task AS C ON C.ParentID = P.Id
)
SELECT * FROM Type
WHERE Id NOT IN (SELECT Typ FROM SubsCTE)