Stačí pre každú (parent, child) asociáciu udržiavať pomocnú reláciu s uzáverom:
x_closure(ancestor_id, descendant_id, distance)
logiku na udržiavanie stačí napísať raz (buď v modeli alebo cez triggery v SQL) a viac to už nikdy nemusíš riešiť.
Ak SQL schému generuješ z nejakého generického modelu, môžeš to celé nechať generovať automaticky pre každú stromovú štruktúru v modeli.
Benefit je, že potom nie si závislý na podpore hierarchických dotazov konkrétnym DBMS.
Jasně, že se to řešit dá, různými způsoby, ale třeba ty triggery už nejsou čisté SQL, ale procedurální berlička ;-)
(A co se týče "nie si závislý na podpore hierarchických dotazov konkrétnym DBMS" - pokud to dělám přes triggery, tak ty jsou DBMS závislé, takže to mně nepomůže. Pomůže řešit to ve vrstvě "nad", což ale zase znamená roundtripy mezi DB klientem a serverem.)
Aké roundtripy? Pri obvyklých typoch dotazov (podstrom od nejakého ancestora do nejakej hĺbky, cesta od nejakého descendanta hore k nejakému ancestorovi) len najoinuješ uzáver, takže sa trochu predraží query execution, ale žiadne client-server roundripy.
To že môžeš uzáver joinovať je ale obrovská výhoda oproti nejakému čisto klientskému riešeniu, pretože poddotazy typy subtree/path potom môžeš využívať v zložitejších dotazoch práve bez potreby zbytočných c-s roundtripov.
Predražia sa tiež úpravy stromovej štruktúry, keďže treba upravovať aj uzáver. V praxi je ale väčšinou časovo kritickejšie dotazovanie než úpravy.
Používam toto riešenie aj na rozsiahle stromy (rádovo milióny uzlov) a zatiaľ ma nesklamalo.
Nezávislost na vlastnostech konkrétní RDBMS se myslím dost přeceňuje, a netýká se to jenom hierarchických dotazů. Téměř vždy vyvíjíte konkrétní DB, investujete do znalostí jejího používání, za komerční databáze dokonce platíte nemalé licence ... takže je trochu absurdní nevyužívat zajímavé vlastnosti které vám poskytuje, a hierarchické dotazy mezi ně myslím patří.
Můj osobní názor je že dokonce i v případech kdy vyvíjíte aplikaci pro více databází, je lepší mít několik persistenčních vrstev využívajících plně každou konkrétní databázi. Alternativou je zápasit s podmnožinou SQL společnou pro všechny databáze (a pak stejně budete chtít přidat podporu další databáze s jinými SQL a máte šmitec).
Význam multi-DB dosť závisí od typu produktu. Ak je to vec, cielená na inhouse prevádzku vo väčších firmách a predaj viacerým klientom, podpora viacerých DB celkom bodne, keďže v tomto prostredí sú dosť bežné rôzne rigidné politiky ohľadom toho, čo sa tam môže nasadiť a prevádzkovať. My pri väčšine aplikácii ako nutné minimum podporujeme Postgres/MySQL/MSSQL (pričom preferred option je Postgres).
Čo poviete nato, keby Bandicoot riešil tento problem nasledujúcim spôsobom?
rel Zamestnanec {
id: int,
meno: string,
manager: int,
}
vsetci: Zamestnanec;
op iteruj(z: Zamestnec, m: rel{manager: int}): Zamestnanec
{
if (ids == empty)
return empty;
priamy := z * m;
manageri := priamy project(id) rename(manager = id);
return priamy + iteruj(z, manageri);
}
fn VsetciPodriadeni(n: {meno: string})
{
manageri := (vsetci * n) project(id) rename(manager = id);
return iteruj(vsetci, manageri);
}
V riešení používam operátory a inline deklarácie relačných typov, ktoré neboli v článku vysvetlené :(
Operátory navyše zataiľ nie sú implementované, ale sú na pláne pre najbližšiu verziu.
Vyzerá to tak, že tento problém je možné vyriešiť, len ak máme ako zapísať cyklus (napr. rekurziou) a podmienky (napr. pomocou if).