Ierarhiile cum ar fi arborii, categoriile dintr-un forum, organigramele si altele asemanatoare sunt uneori dificil de stocat in tabele SQL…..si este de obicei si mai dificil sa extragi ierarhia o data ce ai stocat-o. In acest tutorial vei putea citi despre o metoda usor de inteles si de intretinut, care iti ofera o intreaga ierarhie (sau orice parte a sa) intr-un mod rapid si facil.
In timp ce XML manevreaza datele ierahice destul de bine, SQL-ul relational nu reuseste. Exista cateva metode diferite pentru a modela o structura ierarhica. Cea mai comuna si obisnuita este cunoscuta sub numele de model de adiacenta deoarece utilizeaza referinte la randuri din acelasi tabel:
|
Organigrama modelata de tabelul de mai sus are urmatoarea structura:
1 2 3 4 5 6 |
<span class="hl-identifier">Denis</span> <span class="hl-identifier">Eaton</span><span class="hl-code">-</span><span class="hl-identifier">Hogg</span> <span class="hl-identifier">Bobbi</span> <span class="hl-identifier">Flekman</span> <span class="hl-identifier">Ian</span> <span class="hl-identifier">Faith</span> <span class="hl-identifier">David</span> <span class="hl-identifier">St</span><span class="hl-code">. </span><span class="hl-identifier">Hubbins</span> <span class="hl-identifier">Nigel</span> <span class="hl-identifier">Tufnel</span> <span class="hl-identifier">Derek</span> <span class="hl-identifier">Smalls</span> |
Dupa cum se poate observa, informatia despre parinte (sef) este stocata pe acelasi rand cu cea despre copil (angajat), intr-o coloana adiacenta (BossID). Este un model destul de simplu, usor de inteles de catre oricine, fara a necesita cunoasterea unor teorii relationale complexe. Poti afla cu usurinta cine este seful unei persoane sau colegii acesteia interogand coloana BossID.
Problema incepe cand vrei sa listezi cateva nivele ale unei ierarhii. Pentru a afla seful sefului, trebuie sa unesti tabelul Employees cu el insusi, astfel:
1 2 3 4 |
<span class="hl-reserved">SELECT</span> <span class="hl-identifier">BigBoss</span><span class="hl-code">.</span><span class="hl-identifier">Name</span> <span class="hl-identifier">BigBoss</span><span class="hl-code">, </span><span class="hl-identifier">Boss</span><span class="hl-code">.</span><span class="hl-identifier">Name</span> <span class="hl-identifier">Boss</span><span class="hl-code">, </span><span class="hl-identifier">Employees</span><span class="hl-code">.</span><span class="hl-identifier">Name</span> <span class="hl-identifier">Employee</span> <span class="hl-reserved">FROM</span> <span class="hl-identifier">Employees</span> <span class="hl-reserved">INNER</span> <span class="hl-reserved">JOIN</span> <span class="hl-identifier">Employees</span> <span class="hl-reserved">AS</span> <span class="hl-identifier">Boss</span> <span class="hl-reserved">ON</span> <span class="hl-identifier">Employees</span><span class="hl-code">.</span><span class="hl-identifier">BossID</span><span class="hl-code">=</span><span class="hl-identifier">Boss</span><span class="hl-code">.</span><span class="hl-identifier">EmployeeID</span> <span class="hl-reserved">INNER</span> <span class="hl-reserved">JOIN</span> <span class="hl-identifier">Employees</span> <span class="hl-identifier">BigBoss</span> <span class="hl-reserved">ON</span> <span class="hl-identifier">Boss</span><span class="hl-code">.</span><span class="hl-identifier">BossID</span><span class="hl-code">=</span><span class="hl-identifier">BigBoss</span><span class="hl-code">.</span><span class="hl-identifier">EmployeeID</span> |
Si vei obtine:
|
In mod similar, pentru a afla fiecare nivel al organigramei ar trebui sa unesti tabelul cu el insusi … o optiune nu foarte atractiva daca ai 5 sau mai multe niveluri. Ar fi minunat daca acest lucru s-ar putea realiza automat. Tipul acesta de uniune poarta numele de join recursiv . Desi unele baze de date il accepta (Oracle are sintaxa CONNECT BY), SQL Server nu este una dintre ele.
Pentru a obtine ierarhia, poti defini o procedura stocata care ruleaza printr-un tabel adiacent. Chiar daca functioneaza, este o metoda procedurala care necesita o stiva (utilizarea unui tabel temporar) si poate dura ceva timp pentru a parcurge ierarhii vaste. De asemenea, rezultatul este afisat, nu returnat, astfel incat pentru a-l utiliza sub forma de tabel/ interogare, trebuie sa retii datele intr-un alt tabel temporar.
Oricum, una dintre problemele cu structurile arborescente este complexitatea mult prea mare pentru a efectua sarcini relativ simple, cum ar fi adaugarea, stergerea sau mutarea de noduri in arbore. Chiar si gasirea supraveghetorului imediat superior unui angajat sau subordonatii acestuia necesita 2 auto-join-uri si o subinterogare!
Desi seturile de date ierarhice au o logica foarte atragatoare, fiind usor de efectuat operatii complicate cu arbori asupra acestora, ele sunt mai putin intuitive decat modelul de adiacenta. Este mai dificil de vizualizat o ierarhie sau o organigrama cu ajutorul acestora.
Deci, cum sa reprezinti o ierarhie, utilizand adiacenta si evitand recursivitatea atunci cand este posibil? Este destul de usor de fapt ..o construiesti si o stochezi in tabel!
Aceasta este definitia tabelului pentru structura de tip arbore:
1 2 3 4 5 6 |
<span class="hl-reserved">CREATE</span> <span class="hl-reserved">TABLE</span> <span class="hl-identifier">Tree</span> <span class="hl-brackets">(</span> <span class="hl-identifier">Node</span> <span class="hl-reserved">int</span> <span class="hl-reserved">NOT</span> <span class="hl-reserved">NULL</span> <span class="hl-identifier">IDENTITY</span><span class="hl-brackets">(</span><span class="hl-number">100</span><span class="hl-code">, </span><span class="hl-number">1</span><span class="hl-brackets">)</span><span class="hl-code">, </span><span class="hl-identifier">ParentNode</span> <span class="hl-reserved">int</span><span class="hl-code">, </span><span class="hl-identifier">EmployeeID</span> <span class="hl-reserved">int</span> <span class="hl-reserved">NOT</span> <span class="hl-reserved">NULL</span><span class="hl-code">, </span><span class="hl-identifier">Depth</span> <span class="hl-reserved">tinyint</span><span class="hl-code">, </span><span class="hl-identifier">Lineage</span> <span class="hl-identifier">varchar</span><span class="hl-brackets">(</span><span class="hl-number">255</span><span class="hl-brackets">)</span> <span class="hl-brackets">)</span> |
Tabelul Tree a fost creat separat din cateva motive bine intemeiate pe care le vom discuta mai tarziu, dar acum poti adauga pur si simplu coloanele Depth (adancime) si Lineage(descendenta) la tabelul Employees de mai sus si poti inlocui BossID cu ParentNode. (Nu am utilizat o coloana pentru nume deoarece nu influenteaza structura arborelui, iar daca ai nevoie de ea, o poti crea mai tarziu). Termenii nod si descendenta pot parea nefamiliari dar am vrut sa-i generalizez pe cei de „copil”, „parinte” si „ierarhie”.
Pe baza tabelului Employees, iata cum poate fi completat tabelul Tree:
|
Primul lucru care trebuie facut este popularea nodurilor parinte, ceea ce nu este necesar daca folosesti un singur tabel, dar este oricum usor de facut:
1 2 3 4 5 |
<span class="hl-reserved">UPDATE</span> <span class="hl-identifier">T</span> <span class="hl-reserved">SET</span> <span class="hl-identifier">T</span><span class="hl-code">.</span><span class="hl-identifier">ParentNode</span><span class="hl-code">=</span><span class="hl-identifier">P</span><span class="hl-code">.</span><span class="hl-identifier">Node</span> <span class="hl-reserved">FROM</span> <span class="hl-identifier">Tree</span> <span class="hl-identifier">T</span> <span class="hl-reserved">INNER</span> <span class="hl-reserved">JOIN</span> <span class="hl-identifier">Employees</span> <span class="hl-identifier">E</span> <span class="hl-reserved">ON</span> <span class="hl-identifier">T</span><span class="hl-code">.</span><span class="hl-identifier">EmployeeID</span><span class="hl-code">=</span><span class="hl-identifier">E</span><span class="hl-code">.</span><span class="hl-identifier">EmployeeID</span> <span class="hl-reserved">INNER</span> <span class="hl-reserved">JOIN</span> <span class="hl-identifier">Employees</span> <span class="hl-identifier">B</span> <span class="hl-reserved">ON</span> <span class="hl-identifier">E</span><span class="hl-code">.</span><span class="hl-identifier">BossID</span><span class="hl-code">=</span><span class="hl-identifier">B</span><span class="hl-code">.</span><span class="hl-identifier">EmployeeID</span> <span class="hl-reserved">INNER</span> <span class="hl-reserved">JOIN</span> <span class="hl-identifier">Tree</span> <span class="hl-identifier">P</span> <span class="hl-reserved">ON</span> <span class="hl-identifier">B</span><span class="hl-code">.</span><span class="hl-identifier">EmployeeID</span><span class="hl-code">=</span><span class="hl-identifier">P</span><span class="hl-code">.</span><span class="hl-identifier">EmployeeID</span> |
Si vei obtine:
|
Aceasta operatie trebuie efectuata doar o singura data si apoi nu mai este nevoie sa mentii coloana BossID in tabelul Employees. Partea urmatoare este gasirea nodului radacina al arborelui, cunoscut si ca nivelul cel mai de sus, sau marele sef din organigrama. Acest nod nu are parinte (Null), deci vom incepe de acolo si vom stabili coloana Lineage ca radacina;
1 |
<span class="hl-reserved">UPDATE</span> <span class="hl-identifier">Tree</span> <span class="hl-reserved">SET</span> <span class="hl-identifier">Lineage</span><span class="hl-code">=</span><span class="hl-quotes">'</span><span class="hl-string">/</span><span class="hl-quotes">'</span><span class="hl-code">, </span><span class="hl-identifier">Depth</span><span class="hl-code">=</span><span class="hl-number">0</span> <span class="hl-reserved">WHERE</span> <span class="hl-identifier">ParentNode</span> <span class="hl-reserved">Is</span> <span class="hl-reserved">Null</span> |
O data ce ai terminat, poti actualiza randurile care sunt copiii imediati ai nodului radacina:
1 2 3 4 5 6 7 |
<span class="hl-reserved">UPDATE</span> <span class="hl-identifier">T</span> <span class="hl-reserved">SET</span> <span class="hl-identifier">T</span><span class="hl-code">.</span><span class="hl-identifier">depth</span><span class="hl-code"> = </span><span class="hl-identifier">P</span><span class="hl-code">.</span><span class="hl-identifier">Depth</span><span class="hl-code"> + </span><span class="hl-number">1</span><span class="hl-code">, </span><span class="hl-identifier">T</span><span class="hl-code">.</span><span class="hl-identifier">Lineage</span><span class="hl-code"> = </span><span class="hl-identifier">P</span><span class="hl-code">.</span><span class="hl-identifier">Lineage</span><span class="hl-code"> + </span><span class="hl-reserved">Ltrim</span><span class="hl-brackets">(</span><span class="hl-identifier">Str</span><span class="hl-brackets">(</span><span class="hl-identifier">T</span><span class="hl-code">.</span><span class="hl-identifier">ParentNode</span><span class="hl-code">,</span><span class="hl-number">6</span><span class="hl-code">,</span><span class="hl-number">0</span><span class="hl-brackets">)</span><span class="hl-brackets">)</span><span class="hl-code"> + </span><span class="hl-quotes">'</span><span class="hl-string">/</span><span class="hl-quotes">'</span> <span class="hl-reserved">FROM</span> <span class="hl-identifier">Tree</span> <span class="hl-reserved">AS</span> <span class="hl-identifier">T</span> <span class="hl-reserved">INNER</span> <span class="hl-reserved">JOIN</span> <span class="hl-identifier">Tree</span> <span class="hl-reserved">AS</span> <span class="hl-identifier">P</span> <span class="hl-identifier">ON</span> <span class="hl-brackets">(</span><span class="hl-identifier">T</span><span class="hl-code">.</span><span class="hl-identifier">ParentNode</span><span class="hl-code">=</span><span class="hl-identifier">P</span><span class="hl-code">.</span><span class="hl-identifier">Node</span><span class="hl-brackets">)</span> <span class="hl-reserved">WHERE</span> <span class="hl-identifier">P</span><span class="hl-code">.</span><span class="hl-identifier">Depth</span><span class="hl-code">>=</span><span class="hl-number">0</span> <span class="hl-reserved">AND</span> <span class="hl-identifier">P</span><span class="hl-code">.</span><span class="hl-identifier">Lineage</span> <span class="hl-reserved">Is</span> <span class="hl-reserved">Not</span> <span class="hl-reserved">Null</span> <span class="hl-reserved">AND</span> <span class="hl-identifier">T</span><span class="hl-code">.</span><span class="hl-identifier">Depth</span> <span class="hl-reserved">Is</span> <span class="hl-reserved">Null</span> |
De fapt putem scrie o bucla while pentru a actualiza toti copiii/ nepotii etc. din arbore:
1 2 3 4 5 6 7 8 |
<span class="hl-identifier">WHILE</span> <span class="hl-identifier">EXISTS</span> <span class="hl-brackets">(</span><span class="hl-reserved">SELECT</span><span class="hl-code"> * </span><span class="hl-reserved">FROM</span> <span class="hl-identifier">Tree</span> <span class="hl-reserved">WHERE</span> <span class="hl-identifier">Depth</span> <span class="hl-reserved">Is</span> <span class="hl-reserved">Null</span><span class="hl-brackets">)</span> <span class="hl-reserved">UPDATE</span> <span class="hl-identifier">T</span> <span class="hl-reserved">SET</span> <span class="hl-identifier">T</span><span class="hl-code">.</span><span class="hl-identifier">depth</span><span class="hl-code"> = </span><span class="hl-identifier">P</span><span class="hl-code">.</span><span class="hl-identifier">Depth</span><span class="hl-code"> + </span><span class="hl-number">1</span><span class="hl-code">, </span><span class="hl-identifier">T</span><span class="hl-code">.</span><span class="hl-identifier">Lineage</span><span class="hl-code"> = </span><span class="hl-identifier">P</span><span class="hl-code">.</span><span class="hl-identifier">Lineage</span><span class="hl-code"> + </span><span class="hl-reserved">Ltrim</span><span class="hl-brackets">(</span><span class="hl-identifier">Str</span><span class="hl-brackets">(</span><span class="hl-identifier">T</span><span class="hl-code">.</span><span class="hl-identifier">ParentNode</span><span class="hl-code">,</span><span class="hl-number">6</span><span class="hl-code">,</span><span class="hl-number">0</span><span class="hl-brackets">)</span><span class="hl-brackets">)</span><span class="hl-code"> + </span><span class="hl-quotes">'</span><span class="hl-string">/</span><span class="hl-quotes">'</span> <span class="hl-reserved">FROM</span> <span class="hl-identifier">Tree</span> <span class="hl-reserved">AS</span> <span class="hl-identifier">T</span> <span class="hl-reserved">INNER</span> <span class="hl-reserved">JOIN</span> <span class="hl-identifier">Tree</span> <span class="hl-reserved">AS</span> <span class="hl-identifier">P</span> <span class="hl-identifier">ON</span> <span class="hl-brackets">(</span><span class="hl-identifier">T</span><span class="hl-code">.</span><span class="hl-identifier">ParentNode</span><span class="hl-code">=</span><span class="hl-identifier">P</span><span class="hl-code">.</span><span class="hl-identifier">Node</span><span class="hl-brackets">)</span> <span class="hl-reserved">WHERE</span> <span class="hl-identifier">P</span><span class="hl-code">.</span><span class="hl-identifier">Depth</span><span class="hl-code">>=</span><span class="hl-number">0</span> <span class="hl-reserved">AND</span> <span class="hl-identifier">P</span><span class="hl-code">.</span><span class="hl-identifier">Lineage</span> <span class="hl-reserved">Is</span> <span class="hl-reserved">Not</span> <span class="hl-reserved">Null</span> <span class="hl-reserved">AND</span> <span class="hl-identifier">T</span><span class="hl-code">.</span><span class="hl-identifier">Depth</span> <span class="hl-reserved">Is</span> <span class="hl-reserved">Null</span> |
Nu te ingrijora cu privire la bucla, ruleaza doar o data pentru fiecare nivel al ierarhiei…10 bucle pentru 10 nivele sau generatii. Pentru o corporatie, 10 layere de management reprezinta ceva complex; in cazul unui arbore genealogic, poti afla descendentii unei familii americane pana la Razboiul de Independenta! Si in conditii normale, ar trebui sa rulezi aceasta procedura o singura data. Rezultatul final este:
|
Vei observa ca pentru fiecare nod, este memorata intreaga descendenta pana la radacina. Aceasta inseamna ca gasirea sefului cuiva, sau a sefului sefului, nu necesita vreun join sau recursivitate pentru a crea o lista indentata. De fapt, poate fi obtinuta cu un singurSELECT!
1 2 3 4 |
<span class="hl-reserved">SELECT</span> <span class="hl-reserved">Space</span><span class="hl-brackets">(</span><span class="hl-identifier">T</span><span class="hl-code">.</span><span class="hl-identifier">Depth</span><span class="hl-code">*</span><span class="hl-number">2</span><span class="hl-brackets">)</span><span class="hl-code"> + </span><span class="hl-identifier">E</span><span class="hl-code">.</span><span class="hl-identifier">Name</span> <span class="hl-reserved">AS</span> <span class="hl-identifier">Name</span> <span class="hl-reserved">FROM</span> <span class="hl-identifier">Employees</span> <span class="hl-identifier">E</span> <span class="hl-reserved">INNER</span> <span class="hl-reserved">JOIN</span> <span class="hl-identifier">Tree</span> <span class="hl-identifier">T</span> <span class="hl-reserved">ON</span> <span class="hl-identifier">E</span><span class="hl-code">.</span><span class="hl-identifier">EmployeeID</span><span class="hl-code">=</span><span class="hl-identifier">T</span><span class="hl-code">.</span><span class="hl-identifier">EmployeeID</span> <span class="hl-reserved">ORDER</span> <span class="hl-reserved">BY</span> <span class="hl-identifier">T</span><span class="hl-code">.</span><span class="hl-identifier">Lineage</span><span class="hl-code"> + </span><span class="hl-reserved">Ltrim</span><span class="hl-brackets">(</span><span class="hl-identifier">Str</span><span class="hl-brackets">(</span><span class="hl-identifier">T</span><span class="hl-code">.</span><span class="hl-identifier">Node</span><span class="hl-code">,</span><span class="hl-number">6</span><span class="hl-code">,</span><span class="hl-number">0</span><span class="hl-brackets">)</span><span class="hl-brackets">)</span> |
Daca pastrezi totul intr-un singur tabel nici nu vei avea nevoie de JOIN! Coloana Depth este utila pentru efectuarea indentarii cu functia Space(). Utilizand ORDER BY Lineage, organigrama va fi sortata corespunzator, cu fiecare serie subordonata aflata sub parintele sau. Ordinea de sortare este mentinuta prin valorile ParentNode si poate fi schimbata usor prin actualizarea valorii nodului. Introducerea sau stergerea unui nou nod nu afecteaza restul arborelui. Coloana care retine descendenta poate fi actualizata in mod automat, deci mutarea sau promovarea unui nod este ceva banal.