In multe aplicaţii (comerţul electronic, structura organizatorică a unei firme, teoria grafelor, memorarea cunoştinţelor în web-ul semantic, etc.)
se utilizează tabele de informaţii ce memorează anumite structuri arborescente (colecţie de arbori), aşa cum apar în următorul exemplu.
In continuare se dă o modalitate de memorare a unei astfel de structuri arborescente (tabele ce se pot folosi în comerţul electronic):
produse[cod, denumire] -- cu informatii despre unele produse/grupuri de produse (noduri in structura arborescenta) structura[cod, codp, pozitia] -- cu memorarea structurii arborescente a multimii de produse (legaturile dintre noduri)
Semnificaţia coloanelor din tabelul structura este:
Pentru produsele din rădăcină nu există înregistrare în tabelul "structura" (acestea nu au un produs părinte).
Cu această structură produsele se pot diviza în categorii (pe nivelul 1, care sunt rădăcinile din colecţia de arbori),
subcategorii (pe nivelul 2), sub-subcategorii, etc.
In directorul exemple/StrArborescente/
se află instrucţiuni care crează tabelele amintite şi adaugă date
în aceste tabele.
Probleme:
/* descendentii produsului cu codul 1101 */ select s.cod,denumire from (select * from structura where codp=1101) s inner join produse p on s.cod=p.cod order by s.pozitia,denumire
/* terminalele = produsele care nu sunt "parinte" pentru alte produse */ select produse.cod,denumire from produse inner join structura on produse.cod=structura.cod where structura.cod not in (select distinct codp from structura)
sau o variantă care se evaluează mai repede:
select produse.cod,denumire from produse inner join (select a.cod from structura a left join structura b on a.cod=b.codp where b.codp is null ) s on produse.cod=s.cod order by denumire; /* un cod de produs care apare pe coloana 'cod' in structura si nu apare pe coloana 'codp' */
sau:
select produse.cod,denumire from produse inner join (select a.cod from structura a left join (select distinct codp from structura) b on a.cod=b.codp where b.codp is null) s on produse.cod=s.cod order by denumire
sau:
select a.cod,denumire from produse a left join structura b on a.cod=b.codp where b.cod is null order by denumire; /* un produs care apare pe coloana 'codp' inseamna ca are un 'fiu', deci nu e terminal */ /* altfel, daca nu apare pe coloana codp este 'terminal' */
select cod,denumire from produse where cod not in (select distinct cod from structura) order by denumire; /* cu 'select distinct cod from structura' se determina produsele care au un 'parinte' */
sau o variantă care se evaluează mai repede:
select a.cod,denumire from produse a left join structura b on a.cod=b.cod where b.cod is null order by denumire; /* un produs este 'radacina' daca nu apare pe coloana 'cod' din structura (nu are un 'parinte') */
/* nodurile interioare si terminale */ select produse.cod, denumire, codp from produse inner join structura on produse.cod=structura.cod union /* nodurile radacina */ select a.cod,denumire,0 from produse a left join structura b on a.cod=b.cod where b.cod is null order by 2;
Această ultimă instrucţiune o vom folosi pentru a defini un view în care vor fi incluse toate nodurile,
cele care sunt noduri rădăcină vor avea valoarea 0 pentru părinte (pentru codp):
create or replace view MultProduse as select produse.cod, denumire, codp from produse inner join structura on produse.cod=structura.cod union select a.cod,denumire,0 from produse a left join structura b on a.cod=b.cod where b.cod is null order by 2;
Determinarea tuturor descendenţilor unui nod dat (nu numai a nodurilor incluse imediat
în nodul curent) se face dificil, mai ales dacă se cere
şi nivelul pe care apare fiecare dintre aceste noduri în structura arborescentă.
Rezolvarea acestei probleme se face simplu în Oracle prin folosirea unei variante a instrucţiunii Select-Sql:
Instructiune_select [START WITH conditie1 [CONNECT BY [NOCYCLE] conditie2]]
Aici se observă două clauze noi (START WITH şi CONNECT BY), care pot să apară în orice ordine.
In această variantă a instrucţiunii se cere ca să apară o singură sursă de date (în FROM din SELECT),
care poate să fie şi un view. In această sursă (unică) de date trebuie să existe informaţiile
necesare pentru a deduce structura arborescentă (colecţia de arbori).
Ca sursă de înregistrări se ia mulţimea generată de instrucţiunea select.
Cu clauza "start with" se precizează acele noduri din sursa de date de unde se pleacă la
generarea unei structuri arborescente, iar aceste înregistrări formează primul nivel din structura arborescentă
care se determină (nodurile rădăcină).
Dacă această clauză lipseşte, atunci se iau în
considerare toate înregistrările sursei de date (care vor forma un singur nivel),
iar dacă această clauză este prezentă, atunci se cere şi clauza "connect by", care va adăuga noi nivele
la cele deja determinate.
Clauza "connect by" precizează modul de determinare a legăturilor din structura arborescentă.
Pentru un nod deja determinat în structura arborescentă (cu sau fără START WITH) se determină o legătură spre noi noduri în această structură
prin conditie2 (condiţie ce apare în această clauză): pentru o înregistrare curentă se determină
înregistrările legate de aceasta (din aceeaşi sursă de date) şi care îndeplinesc conditia2 cu valoarea true.
Dacă p este un nod la care s-a ajuns, atunci un nod fiu f al lui p se adaugă
la lista care se determină (pentru un nou nivel) dacă valoarea conditie2 (care depinde de p şi f) este true. Inregistrarea
părinte curentă p (iniţial poate fi una de start, determinată de condiţia din "start with") conţine valori
pentru p şi f (folosite în conditie2), din care numai una este prioritară, şi anume
aceea care este precedată de operatorul unar prior.
Pentru view-ul MultProduse precizat mai sus, se poate lua în considerare o înregistrare
oarecare (cu o valoare dată pentru cod, sau pentru codp, sau pentru denumire) şi se determină alte înregistrări
din acelaşi view prin condiţia cod=codp (cod sau codp este fixat pentru o înregistrare curentă şi se determină
toate înregistrările legate de acesta prin condiţia dată). Pentru a preciza care valoare se ia
din înregistrarea curentă (care are prioritate), una din coloane se va preceda de prior.
Pentru exemplificare, considerăm o parte dintr-o structură arborescentă, dată
în figura următoare. Inregistrările din viewul MultProduse, corspunzătoare acestor noduri, sunt trecute în tabelul alăturat.
cod | codp | denumire |
1 | 0 | ... |
2 | 1 | ... |
3 | 2 | ... |
4 | 2 | ... |
Sa presupunem că se ia o înregistrare de start, care va fi pe nivelul 1. Inregistrări "fiu" sau
"parinte" determinate în modul descris mai sus sunt pe nivelul 2 (nivelul următor).
Dacă pentru aceste înregistrări "fiu" sau "parinte" determinate se mai continuă, se pot
determina înregistrările "fiu" sau "parinte" pentru cele determinate anterior, deci
înregistrările aflate pe nivelul 3. In expresiile din instrucţiunea select se poate
folosi pseudocoloana level, care furnizează nivelul descris mai sus pentru
o înregistrare curentă.
Exemple:
select level, cod, codp ,denumire from multproduse start with codp=0 connect by prior cod=codp;
select level,lpad('*',3*(level-1),'*') || to_char(cod) || ' ' || denumire, codp from multproduse start with codp=0 connect by prior cod=codp;
select level, cod, codp ,denumire from multproduse start with cod=400 connect by cod=prior codp;
Pentru a memora arcele unui graf orientat, cu vârfurile etichetate cu caractere, vom folosi următorul tabel:
create table graf(inceput char(1),final char(1));
Vom introduce unele arce în acest graf:
insert into graf values('2','1'); insert into graf values('1','6'); insert into graf values('6','5'); insert into graf values('5','2'); insert into graf values('6','7'); insert into graf values('2','3'); insert into graf values('3','4'); insert into graf values('5','4'); insert into graf values('4','8'); insert into graf values('9','8'); insert into graf values('3','9'); insert into graf values('4','a'); insert into graf values('8','b');
|
Pentru a determina vârfurile la care se ajunge dintr-un vârf dat se poate folosi instrucţiunea:
select lpad(' ', level-1) || inceput || '->' || final from graf start with inceput = '3' connect by inceput =prior final;
si se obtine:
3->4 4->8 8->b 4->a 3->9 9->8 8->b
Cu aceeaşi instrucţiune, dar cu: "start with inceput = '1' se obţine o eroare:
ERROR: ORA-01436: CONNECT BY loop in user data
Pentru a elimina o astfel de eroare generată de apariţia ciclurilor, se poate folosi clauza "NOCYCLE":
select lpad(' ', level-1) || inceput || '->' || final from graf start with inceput = '1' connect by nocycle inceput =prior final;
care va genera următorul răspuns:
1->6 6->5 5->2 2->1 2->3 3->4 4->8 8->b 4->a 3->9 9->8 8->b 5->4 4->8 8->b 4->a 6->7
Pentru a determina toate nodurile din graf de la care se ajunge în nodul cu eticheta '4' avem instrucţiunea:
select level,lpad(' ', level-1) || inceput || '->' || final from graf start with final = '4' connect by nocycle prior inceput = final;
cu rezultatul:
3->4 2->3 5->2 6->5 1->6 5->4 6->5 1->6 2->1
Cu ajutorul pachetului DBMS-RANDOM şi cu clauza CONNECT BY se pot genera date aleatoare de test,
ca în exemplul următor:
select round(dbms_random.value(1,10000)) cod, dbms_random.string('U',20) valoare from dual connect by level<=1000;
Valorile astfel generate se pot include într-un tabel memorat.
Se pleacă de la singură înregistrare existentă în tabelul dual si se verifică dacă este indeplinită condiţia din Connect by. Condiţia folosită este level<=1000, deci plecând de la singura înregistrare aflată în dual, cu level=1, se determină tot această înregistrare, pentru level=2, apoi de la aceasta se determină următoarea (tot aceasta), cu level=3, etc.