SQL Server 2008 újdonságok 11. - HierarchyId adattípus 2.

Mire jó a HierarchyID? Vannak műveletek, amelyeket gyorsabban lehet végrehajtani a segítségével, mivel a node-ok elérési útja van enkódolva az idben, így a felindexelt id alapján egyes lekérdezések hatékonyak lehetnek.

Nézzük meg pl. hogyan keresnénk meg egy adott ember összes direkt vagy indirekt beosztottját? Azaz, az adott node alatti részfát szeretnénk kiválasztani (az előző rész adataira alapozok).


declare @manager hierarchyid = (select OrgNode
from HumanResources.NewOrg
where LoginID = 'adventure-works\terri0')

--select @manager.ToString()

select
cast(OrgNode as varchar(50)) as OrdPath,
EmployeeID, LoginID, ManagerID, Title
from HumanResources.NewOrg
where @manager.IsDescendant(OrgNode) = 1
order by OrdPath

Kikeressük terri0 HierarchyID-jét, majd az IsDescendant metódus segítségével leszűrjük az utódait. Gyerekek, unokák, stb. (kicsit bizarr, hogy pont Terri nevű emberről szól a példánk… :). A függvény magát a kiinduló node-ot is visszaadja, azaz a DescendantOrSelf név precízebb név lenne (persze ez egy CTP verzió, ki tudja, hogy lesz még a véglegesben).
A kimenet:


OrdPath                        EmployeeID  LoginID                        ManagerID   Title
------------------------------ ----------- ------------------------------ ----------- ------------------------------
/2/                            12          adventure-works\terri0         109         Vice President of Engineering
/2/1/                          3           adventure-works\roberto0       12          Engineering Manager
/2/1/1/                        4           adventure-works\rob0           3           Senior Tool Designer
/2/1/2/                        9           adventure-works\gail0          3           Design Engineer
/2/1/3/                        11          adventure-works\jossef0        3           Design Engineer
/2/1/4/                        158         adventure-works\dylan0         3           Research and Development Manag
/2/1/4/1/                      79          adventure-works\diane1         158         Research and Development Engin
/2/1/4/2/                      114         adventure-works\gigi0          158         Research and Development Engin
/2/1/4/3/                      217         adventure-works\michael6       158         Research and Development Manag
/2/1/5/                        263         adventure-works\ovidiu0        3           Senior Tool Designer
/2/1/5/1/                      5           adventure-works\thierry0       263         Tool Designer
/2/1/5/2/                      265         adventure-works\janice0        263         Tool Designer
/2/1/6/                        267         adventure-works\michael8       3           Senior Design Engineer
/2/1/7/                        270         adventure-works\sharon0        3           Design Engineer

Az OrdPathból jól látható, de az EmployeeID-k és ManagerID-k alapján szemre is kikereshető, hogy az összes utód szerepel a listában.

A lekérdezés költségének elemzéséhez kiveszem az OrdPath oszlopot, és a order by-t is, hogy tisztán a szűrés költségét lássuk. Illetve rakok egy indexet a LoginID oszlopra, hogy az első sor szűrése gyors legyen:


create nonclustered index IDX_LoginID
on HumanResources.NewOrg(LoginID)

A csupasz lekérdezés:


declare @manager hierarchyid = (select OrgNode
from HumanResources.NewOrg
where LoginID = 'adventure-works\terri0')

select
EmployeeID, LoginID, ManagerID, Title
from HumanResources.NewOrg
where @manager.IsDescendant(OrgNode) = 1

A végrehajtási terv:

Szűrés végrehajtási terve HierarchyID alapján

Mint látható (kattintani kell a képre) az IsDescedantra fel van készítve az optimizer (!), és a lekérdezést a leghatékonyabb módon, index seek-kel hajtja végre (alul, jobb oldalt). Nézzük meg a szűrőfeltételt is az index operátorhoz:


Seek Keys[1]:
Start: [AdventureWorks].[HumanResources].[NewOrg].OrgNode >= Scalar Operator([@manager]),
End: [AdventureWorks].[HumanResources].[NewOrg].OrgNode <= Scalar Operator([@manager].DescendantLimit())

Látható, hogy kihasználja azt, hogy az összes utód egy bagázsban van, mivel az OrdPath így van rendezve (emlékezzünk az előző részből, a HieararchyID oszlopunkon volt egy clustered index: constraint PK_NewOrg_OrgNode primary key clustered (OrgNode)). A DescendantLimit egy belső függvény, ez jelzi, hol váltanak szintet, meddig kell keresni. Ügyes. A lekérdezés költsége minimális, az első és a második rész összege 6.6 ms (két index seek). Persze, az igazán izgalmas, mekkora ennek a költsége a rekurzív CTE megoldáshoz képest, ami így nézne ki:


with Descendants(EmployeeID, LoginID, ManagerID, Title)
as
(
select EmployeeID, LoginID, ManagerID, Title
from HumanResources.NewOrg
where LoginID = 'adventure-works\terri0'

union all

select
e.EmployeeID,
e.LoginID,
e.ManagerID,
e.Title
from HumanResources.NewOrg as e
join Descendants as d
on e.ManagerID = d.EmployeeID
)

select * from Descendants

Legyünk igazságosak, a HieararchyID oszlopon volt egy clustered index, az persze megbikázta a lekérdezést mint állat. Szegény CTE-s megoldásunk meg join-ol keményen, kapjon hát hozzá olyan indexeket, amelyek rendesen megtámogatják. Mivel a ManagerID nem túl szelektív (egy adott ManagerID-re a tábla jelentős része visszajöhet), ezért annak included column-ot is tartalmazó nc indexet adok, hogy cover query-t tudjon csinálni a szerver.


create unique nonclustered index IDX_EmployeeID
on HumanResources.NewOrg(EmployeeID)
go

create nonclustered index IDX_ManagerID
on HumanResources.NewOrg(ManagerID) include (EmployeeID, LoginID, Title)

(Finomság: az OrgNode nincs benne az indexben, mivel az a clustered index kulcsa, így mindenképpen benne van minden nc indexben is, így kár lenne kétszer belerakni.)

Ezen megoldásban a kimenet sorrendje más, de az adatok benne ugyanazok, és mivel most is csak a szűz lekérdezés költségére koncentrálunk, az order by-t itt is kihagytuk.

A rekurzív CTE végrehajtási terve

A terv költsége 21 ms. Ezekkel az adatokkal a HierarchyID-s megoldás 3x gyorsabb. Minimum, ugyanis míg az első megoldás 5 lapolvasást igényelt, a CTE-s 118-at, és még egy temp táblát is használt! A gyakorlatban ez azt jelenti, hogy terhelt szervernél sokkal lassabb lesz, mint a 3-as szorzó.

Összegezve, ha egy node utódjait reprezentáló részfát akarunk leválogatni, akkor egy rendesen karbantartott és felindexelt HierarchyID oszlop hatékony lekérdezéseket tesz lehetővé, hatékonyabbat, mint a hagyományos rekurzív megoldás.

Zárásul egy figyelmeztetés. NEM minden esetben gyorsabb a HierarchyID a hagyományos rekurzív kompozícióval szemben, szóval ne halljam vissza, hogy soci azt mondta, dobjuk ki a ManagerID-t, és használjunk HiearchyID-t. Nem, a következő részben mutatok olyan esetet, amikor a hagyományos relációs megoldás gyorsabb. Nyilván olyan lekérdezést kell megfogalmazni, ami nem tudja kihasználni a HiearchyID mélységi bejárás alapú indexelését.

7 Responses to “SQL Server 2008 újdonságok 11. - HierarchyId adattípus 2.”

  1. Budai Péter Says:

    Jópofa dolog ez a hierarchyID, mindenképpen áttekinthetőbbé teszi a kódot, meg a próba-lekérdezéseket is egy fából. Engem általában még az szokott zavarni, hogy egy parent/managerID-s táblát nagyon kényelmetlen kézzel módosítgatni az SQL IDE-ről. Erre van esetleg valami tipped, hogyan érdemes?

  2. Soczó Zsolt Says:

    Még a sima táblákat se mindig egyszerű, nem is erre való az SQL IDE.

    Hiearchiákhoz valami custom app kell, másként kézzel nehéz belemászni.

  3. Atis Says:

    télleg törölve lett innen a korábbi “hozzászólásom”?

  4. Soczó Zsolt Says:

    Tudatosan biztos nem, de a spamfilter belenyúlhatott. Utánanézek a kukában.

  5. Soczó Zsolt Says:

    Nincs a kukában, de 5 nap után törlődnek belőle a tételek, ha korábban írtál, és a spamba került, akkor annak már sajnos lőttek.

  6. Atis Says:

    csak írtam egy mailt, azt szerettem volna megkérdezni, megkaptad-e, illetve - ha nem - akkor hová írhatok, amit el is olvasol :)

  7. Soczó Zsolt Says:

    zsolt.soczo kukac gmail.com.

    Ezt állandóan olvasom. :)

Leave a Reply