Hierarchikus adatok kezelése SQL Server segítségével – 1. rész
Az utóbbi időben igen sok esetben kellett hierarchikus adatokkal foglalkoznom, illetve interjúk során is fel szoktam tenni ezzel kapcsolatban kérdéseket. Az alábbiakban szeretném bemutatni, hogy a különböző SQL Server verziók esetében ezen adattípusokat hogyan lehet/érdemes kezelni.
Mi lehet hierarchikus adat?
Ezt az egyszerű kérdést talán a legjobban egy példával lehet megválaszolni: ilyen adat lehet egy családfa vagy egy vállalat szervezeti felépítése. Maga a hierarchia jelentése a Wikipedia szerint:
A hierarchia (görögül: hierarchia (ἱεραρχία) (….)) olyan elrendezése elemeknek (tárgyak, nevek, értékek, kategóriák, stb.), amelyben az elemek egymáshoz képest alul, felül vagy egy szinten helyezkednek el. Absztraktabb módon: hierarchia egyszerűen egy rendezett halmaz vagy egy aciklikus irányított gráf.
Alapvetően 3 féle módon tárolhatunk hierarchikus adatokat:
Saját magára hivatkozó (SELF JOIN) tábla segítségével: ez működik SQL 2005 előtti verziókkal is, de csak az SQL Server 2005 és újabb verziók támogatják a rekurzív lekérdezéseket. Ennek a funkciónak a hiánya komplex, nehezen karbantartható lekérdezésekhez vezethet.
XML adattípus használatával: csak SQL Server 2005 és újabb verziók esetén.
hierarchyid adattípussal: csak SQL Server 2008 és újabb verziók esetén érhető el ez az adattípus. A három módot egy példán keresztül mutatnám be: egy cég szervezeti feléítését kell eltárolni, ahol több eseményre és információra vagyok kíváncsi:
Szeretném megtudni, hogy egy-egy személy hanyadik szinten van a szervezeti felépítésben.
Mi történik akkor, ha valakit áthelyeznek? Itt egy másik kérdés is felmerül: ki veszi át a helyét, illetve szervezetileg hozzátartozó személyek is mennek vagy nem?
Saját magára hivatkozó (SELF JOIN) tábla
Talán ez a legegyszerűbb megvalósítása a hierarchikus adatok tárolásának, de nem feltétlenül a legoptimálisabb is. Talán a legnagyobb előnye, hogy az SQL Server 2005 előtti verziókkal is működik. Az alábbi tábla struktúra egy példa az ilyen típusú adatok tárolásához.
1CREATE TABLE [dbo].[EmployeeSelfJoin]
2(
3 [EmployeeID] int NOT NULL PRIMARY KEY,
4 [ManagerID] int NULL REFERENCES [dbo].[EmployeeSelfJoin]([EmployeeID]),
5 [EmployeeName] nvarchar(255)
6
7);
8GO
Ez gyakorlatilag a GetLevel() függvénynek felel meg a hierarchyid típusnál. Mi történik akkor, ha valakit áthelyeznek?
A hierarchiában való mozgatásnak 2 esete van:
- Az alárendelt elemek is mennek a mozgatott elemmel,
- Az alárendelt elemek nem mennek a mozgatott elemmel, másik elem alárendeltségébe kerülnek
vagy nem😀. Az első esetben egyszerű a dolgunk mivel csak egy helyen – ManagerID – kell változtatni az adatokon:
1UPDATE [dbo].[EmployeeSelfJoin] SET [ManagerID] = 1
2WHERE [EmployeeID] = 160;
A második esetben már nem olyan egyszerű a helyzet. Ez a fajta módszer nem engedi meg, hogy “Manager” nélkül maradjanak elemek, így itt már 2 lépésben van lehetőség a változást megcsinálni: a sorrend ahogyan csináljuk csak az alkalmazástól és az üzleti szabályoktól függ (+ tranzakciós szint!). Első lépésben az alárendelt elemeket rendezem át, majd ugyan úgy, mint az első esetben a kiválasztott elemet helyezem át:
1UPDATE [dbo].[EmployeeSelfJoin] SET [ManagerID] = 26
2WHERE [ManagerID] = 160;
3
4UPDATE [dbo].[EmployeeSelfJoin] SET [ManagerID] = 1
5WHERE [EmployeeID] = 160;
Konvertálás egyik típusból a másikba
A fenti példákat az AdventureWorks2008R2 adatbázis HumanResources.Employee táblájával készítettem el. Az első esetben hierarchyid típusból kellett SELF JOIN típusú táblába adatokat töltenem. Az adatokat az alábbi lekérdezéssel lehetett előállítani:
1SELECT
2 P.[BusinessEntityID],
3 NULL,
4 P.[LastName] + ' , ' + P.[FirstName]
5FROM
6 [HumanResources].[Employee] E
7JOIN
8 [dbo].[Person] P ON E.[BusinessEntityID] = P.[BusinessEntityID]
9WHERE
10 E.[OrganizationNode].GetAncestor(1) IS NULL
11UNION
12SELECT
13 P.[BusinessEntityID],
14 MGR.[BusinessEntityID],
15 P.[LastName] + ' , ' + P.[FirstName]
16FROM
17 [HumanResources].[Employee] E
18JOIN
19 [dbo].[Person] P ON E.[BusinessEntityID] = P.[BusinessEntityID]
20CROSS APPLY
21 (SELECT [BusinessEntityID] FROM [HumanResources].[Employee] WHERE
22 [OrganizationNode] = E.[OrganizationNode].GetAncestor(1)) AS [MGR];