12. blok
Pokročilé konstrukce SQL dotazů - část II
Studijní cíl Tento blok je věnován pokročilým konstrukcím SQL dotazů, které umožní psát efektivní kód. Pozornost je věnována vytváření pohledů v rámci dotazů pomocí klauzule WITH a procházení stromovou strukturou. Doba nutná k nastudování
2 - 3 hodiny
Průvodce studiem Při studiu tohoto bloku se předpokládá, že čtenář je s DML příkazy jazyka SQL. 1. In-line pohledy Pokud se poohlédneme zpět do kurzu IDAS1, najdeme v lekci o pohledech kapitolu hovořící o In-line pohledech. In-line pohled je vnořený dotaz, který má vlastní alias. V příkazu SELECT se s in-line pohledem pracuje stejně, jako s běžnou tabulkou. Syntaxe in-line pohledu je: SELECT * FROM (SELECT * FROM tabulka) nazev_pohledu;
Použití in-line pohledů jsme prezentovali na jednoduchém příkladu. SELECT vProduktyDodavatele.nazev, ROUND(AVG(vProduktyDodavatele.cena),2) as prumerna_cena FROM (SELECT dodavatele.nazev, produkty.oznaceni, produkty.produkt_id, dodavatele.dodavatel_id, produkty.cena FROM dodavatele JOIN produkty ON produkty.dodavatel_id = dodavatele.dodavatel_id ) vProduktyDodavatele GROUP BY vProduktyDodavatele.nazev;
David Žák, Jiří Zechmeister IDAS2/12 – Pokročilé konstrukce SQL dotazů - část II
1
Pro správné pochopení si nyní dotaz rozebereme. Nejdříve byl vyhodnocen in-line pohled vProduktyDodavatele. Výstupem z toho pod-dotazu byla množina výsledků, na kterou se provedl druhý dotaz. Stejný příklad by bylo možné řešit i pomocí klasických pohledů: CREATE VIEW vProduktyDodavatele AS SELECT dodavatele.nazev, produkty.oznaceni, produkty.produkt_id, dodavatele.dodavatel_id, produkty.cena FROM dodavatele JOIN produkty ON produkty.dodavatel_id=dodavatele.dodavatel_id WITH READ ONLY; SELECT vProduktyDodavatele.nazev, ROUND(AVG(vProduktyDodavatele.cena),2) as prumerna_cena FROM vProduktyDodavatele GROUP BY vProduktyDodavatele.nazev;
2. Dočasné pohledy - klauzule WITH Jedním z řešení pro zvýšení čitelnosti a srozumitelnosti dotazů je použití klauzule WITH - kdy hovoříme o Common Table Expression (CTE). CTE je dočasný pohled (temporary view) a používá se zejména v případech, kdy nějaký poddotaz je v rámci dotazu použit vícekrát, nebo - jak si ukážeme v následující kapitole, pro konstrukci rekurzivních dotazů. Výše uvedený příklad můžeme například zapsat následovně s použitím CTE: WITH vProduktyDodavatele AS (SELECT dodavatele.nazev, produkty.oznaceni, produkty.produkt_id, dodavatele.dodavatel_id, produkty.cena FROM dodavatele JOIN produkty ON produkty.dodavatel_id=dodavatele.dodavatel_id) SELECT vProduktyDodavatele.nazev, ROUND(AVG(vProduktyDodavatele.cena),2) David Žák, Jiří Zechmeister IDAS2/12 – Pokročilé konstrukce SQL dotazů - část II
2
as prumerna_cena FROM vProduktyDodavatele GROUP BY vProduktyDodavatele.nazev;
Samozřejmě tímto řešením dostaneme shodný výsledek jako v přechozím případě. CTE významně zvyšují přehlednost dotazů. Navíc není nutné vytvářet pohledy ve smyslu samostatných databázových objektů, ale definovat je jako dočasné pohledy v rámci konkrétních dotazů. Klauzule WITH tedy umožňuje přiřadit název určitému poddotazu uvedenému před vlastním (hlavním) dotazem SELECT. V něm se pak můžete odkazovat na tento dočasný pohled zadáním jeho jména – obdobně jako při práci s pohledy. Oracle optimalizuje zpracování dočasného pohledu uvedeného jména, pracuje s ním buď jako s inline pohledem nebo jako s dočasnou tabulkou. Jednotlivé dočasné pohledy se mohou odkazovat na předchozí - dříve definované dočasné pohledy, stejně jako se na ně odkazuje hlavní dotaz. Použití klauzule WITH je velice vhodné v případě, kdy je výsledek dočasného pohledu odkazován vícekrát v rámci jediného dotazu, když například průměrné hodnoty zjištěné dočasným pohledem musí být několikrát porovnávány během vykonávání dotazu a běžné řešení by znamenalo bud zřízení samostatného pohledu nebo uvedení několika totožných vnořených dotazů v rámci hlavního dotazu. Obecná syntaxe je: WITH
AS (subquery_sql_statement) [, AS (subquery_sql_statement)… ] SELECT FROM … ;
David Žák, Jiří Zechmeister IDAS2/12 – Pokročilé konstrukce SQL dotazů - část II
3
3. Hierarchické dotazy (Oracle 10g) Hierarchické dotazy slouží k získání dat seřazených v hierarchickém pořadí. Dotaz je možné použít také nad spojením dvou či více tabulek. Základní princip Jak je zobrazeno na následujícím obrázku, hierarchický dotaz vyžaduje navíc pouze klauzuli CONNECT BY, která definuje, přes které atributy se bude provádět spojení hierarchických dat. K definici nadřazeného atributu je k dispozici klauzule PRIOR. Pomocí konstrukce START WITH, můžeme definovat, od kterého záznamu má být dotaz vyhodnocován. Klauzule NOCYCLE zamezí cyklickému procházení dat. Ke zjištění, na kterém záznamu vzniká cyklický odkaz, slouží pseudo-sloupec CONNECT_BY_ISCYCLE, který obsahuje hodnotu 1 pro řádky, kde k cyklickému odkazu došlo jinak 0.
V rámci hierarchických dotazů, je k dispozici také pseudo-sloupec LEVEL, který zobrazuje úroveň záznamu v hierarchické posloupnosti. Dále máme k dispozici další funkce a operátory pro práci nad hierarchicky organizovanými daty. Pseudo-sloupec CONNECT_BY_ISLEAF Pseudosloupec, který nabývá hodnotu 1, pokud záznam je v hierarchické úrovni listem. Jinak nabývá hodnotu 0. SELECT ..., CONNECT_BY_ISLEAF as jeListem, ... FROM ... CONNECT BY ...
David Žák, Jiří Zechmeister IDAS2/12 – Pokročilé konstrukce SQL dotazů - část II
4
Pseudo-sloupec CONNECT_BY_ROOT Operátor, který vrátí pro daný sloupec hodnotu na nejvyšší hierarchické úrovni. SELECT ..., CONNECT_BY_ROOT jmeno as jmenoReditele, ... FROM ... CONNECT BY ...
Pseudo-sloupec SYS_CONNECT_BY_PATH Funkce, která vrací kompletní cestu sestavenou pomocí sloupce, který vstupuje jako atribut a oddělovače, který vstupuje jako druhý atribut. SELECT ..., SYS_CONNECT_BY_PATH(jmeno, '/') as jmenoCesta, ... FROM ... CONNECT BY ...
Pro třídění dat se zachováním hierarchické úrovně, je možné použít v klauzuli ORDER BY klíčové slovo SIBLINGS, které toto zajišťuje. Data jsou na výstupu řazena podle atributů uvedených v klauzuli GROUP BY, ale vždy pouze v rámci množiny, která má stejného předka. V dotazu je klauzule použita následovně. SELECT ... FROM ... CONNECT BY ... ORDER BY SIBLINGS jmeno ASC
David Žák, Jiří Zechmeister IDAS2/12 – Pokročilé konstrukce SQL dotazů - část II
5
Postup vyhodnocení hierarchických dotazů 1. Nejdříve jsou vyhodnocena spojení tabulek, pokud jsou k dispozici. 2. Řádky vyhovující podmínce ve START WITH jsou považovány za kořenové řádky na první úrovni vnoření 3. Pro každý řádek na úrovni i se rekurzivně hledají přímí potomci vyhovující podmínce v klauzuli CONNECT BY na úrovni i+1, řádek předka se v podmínce označuje klíčovým slovem PRIOR 4. Na závěr jsou odstraněny řádky nevyhovující podmínce uvedenou v klauzuli WHERE 5. Pokud není definováno třídění, odpovídá pořadí průchodu pre-order Příklad použití Fungování hierarchických dotazů si můžeme ukázat na jednoduchém příkladu. Máme tabulku, která popisuje osoby. Obsahuje atributy jméno, příjmení, adresu, datum narození a pohlaví označené písmenem M pro muže a písmenem Z pro ženu. Dále je zde také přítomen atribut ID, sloužící jako jednoznačná identifikace každé osoby, která se v tabulce nachází. Součástí tabulky jsou také identifikátory (ID) otce a matky každé osoby. Pokud tedy vezmeme v úvahu následující generační strom, tabulku si můžeme představit následovně: Jarmila Malá
Jan Malý
Martin Veselý
Petr Vyndal
Petra Veselá
Jana Mala
Jan Veselý
Jan Vyndal
Petr Ostnatý
Robert Ostnatý
Ivana Ostnatá
Marta Velká
Lenka Ostnatá
Obr. 1 - Generační strom pro interpretaci hierarchických dotazů
David Žák, Jiří Zechmeister IDAS2/12 – Pokročilé konstrukce SQL dotazů - část II
6
ID
Jméno
Příjmení
Pohlaví
1
Jan
Malý
M
2
Jarmila
Malá
3
Jana
4
Datum narození
ID otce
ID matky
10. 3. 1898
(null)
(null)
Z
4. 6. 1901
(null)
(null)
Malá
Z
3. 12. 1920
1
2
Jarmil
Veselý
M
9. 9. 1921
(null)
(null)
5
Petr
Ostnatý
M
14. 1. 1918
(null)
(null)
6
Petra
Veselá
Z
5. 5. 1941
4
3
7
Jan
Veselý
M
2. 10. 1945
4
3
8
Robert
Ostnatý
M
8. 2. 1948
5
3
9
Petr
Vyndal
M
25. 7. 1947
(null)
(null)
10
Marta
Velká
Z
13. 9. 1950
(null)
(null)
11
Jan
Vyndal
M
14. 12. 1963
9
6
12
Ivana
Ostnatá
Z
4. 7. 1972
8
10
13
Lenka
Ostnatá
Z
14. 4. 1975
8
10
Tabulka Ilustrační obsah tabulky osoby
Nyní bude ilustrován rozdíl mezi použitím běžného dotazu a použitím hierarchického dotazu. Veškeré dotazy budou pracovat s tabulkou osoby, která je znázorněna výše. Dotazy jsou platné pro databázový server Oracle verze 10g. První příklad použití hierarchických dotazů bude nalezení společných potomků Jarmily Malé a Jana Malého. Nejdříve zápis pomocí běžného dotazu bez použití klauzule CONNECT BY. SELECT osoby.* FROM osoby WHERE id_matky = ( SELECT id FROM osoby WHERE jmeno='Jarmila' AND prijmeni='Malá' ) AND id_otce = ( SELECT id FROM osoby WHERE jmeno='Jan' AND prijmeni='Malý' );
David Žák, Jiří Zechmeister IDAS2/12 – Pokročilé konstrukce SQL dotazů - část II
7
Klasický dotaz využívá dvou vnořených dotazů, pro zjištění identifikátoru osob, které následně vstupují do podmínky. Pokud použijeme hierarchický dotaz s klauzulí CONNECT BY, zbavíme se vnořených dotazů. SELECT osoby.* FROM osoby CONNECT BY id_matky = PRIOR id AND id_otce = PRIOR id START WITH (jmeno, prijmeni) IN (('Jarmila','Malá'),('Jan','Malý')) WHERE level = 2
Na první pohled je hierarchický dotaz přehlednější než dotaz běžný. Není třeba žádných vnořených dotazů. Za klauzulí START WITH je použit konstruktor řádkové hodnoty pro nalezení potřebných kombinací jména a příjmení. Podmínka na konci dotazu říká, že mají být zobrazeny pouze záznamy na úrovni 2, což jsou potomci osob, které jsou zadané za klauzulí START WITH. Výstupem z následujícího příkladu jsou všichni vnuci Jarmily Malé. Běžným dotazem je možné výsledku dosáhnout za použití tří do sebe vnořených dotazů. Dotaz na nejnižší úrovni zjišťuje identifikátor Jarmily Malé, druhý dotaz zjišťuje všechny děti Jarmily Malé a teprve až třetí dotaz vrátí požadovaný výsledek, tedy vnuky. SELECT osoby.* FROM osoby WHERE id_otce OR id_matky IN ( SELECT id FROM osoby WHERE id_matky = ( SELECT id FROM osoby WHERE jmeno='Jarmila' AND prijmeni='Malá' ) ) AND pohlavi='M';
Naproti tomu hierarchický dotaz je téměř stejný jako u prvního příkladu, pouze se liší podmínky za klauzulí START WITH a WHERE.
David Žák, Jiří Zechmeister IDAS2/12 – Pokročilé konstrukce SQL dotazů - část II
8
SELECT osoby.* FROM osoby CONNECT BY PRIOR id = id_otce AND PRIOR id = id_matky START WITH jmeno='Jarmila' AND prijmeni='Malá' WHERE level=3 AND pohlavi='M';
Posledním příkladem použití hierarchických dotazů je zjištění všech žen, které jsou pokrevně spřízněny s párem Jarmila Malá a Jan Malý. Jedná se na první pohled o primitivní úlohu, ale běžným dotazem ji není možné realizovat v přehledné formě. Použití hierarchického dotazu je však stále jednoduché a mění se pouze podmínky. Pro definování počátku hierarchické úrovně, je opět využito konstruktoru řádkové hodnoty. SELECT osoby.* FROM osoby CONNECT BY id_matky = PRIOR id AND id_otce = PRIOR id START WITH (jmeno, prijmeni) IN ('Jarmila','Malá'),('Jan','Malý')) WHERE pohlavi='Z'
Jak vyplývá z uvedených příkladů, některá zadání již běžnými dotazy není možné řešit a použití hierarchických dotazů je nezbytné. Úlohy, na které je ještě možné běžné dotazy použít, působí při použití hierarchických dotazů více přehledně a minimalizuje se tak možnost chyb při psaní dotazů. Na rozdíl od běžných dotazů je však tento typ značně náročnější na výpočetní výkon databázového stroje. Pokud byste chtěli výstup z dotazu použít pro zobrazení ve formě rozbalovací hierarchie tak, jak to třeba dělá u souborů Windows Explorer, bude se vám hodit i pseudosloupec CONNECT_BY_ISLEAF, který určuje, zda je aktuální záznam na poslední úrovni hierarchie (CONNECT_BY_ISLEAF=1) nebo zda má podřízené záznamy (CONNECT_BY_ISLEAF=0). Níže uvedený příklad využívají i hodnoty LEVEL pro zarovnání všech dceřiných prvků pomocí mezer vložených před názvy jména osob. SELECT
lpad(' ',level*3)||PRIJMENI||' '||JMENO name, SYS_CONNECT_BY_PATH(PRIJMENI, '/') path, CONNECT_BY_ROOT PRIJMENI topmgr, CONNECT_BY_ISLEAF isleaf, level FROM A_HR.ZAMESTNANCI CONNECT BY MANAZER_ID = PRIOR ZAMESTNANEC_ID START WITH MANAZER_ID is null ORDER SIBLINGS BY PRIJMENI;
David Žák, Jiří Zechmeister IDAS2/12 – Pokročilé konstrukce SQL dotazů - část II
9
4. Hierarchické dotazy dle ANSI SQL Od verze databázového serveru Oracle 11g je k dispozici také alternativa pro konstrukci hierarchických dotazů, která je pro databázový stroj přirozenější a může lépe optimalizovat a plánovat jeho provádění. Tato alternativa je zajištěna pomocí rekurzivního volání pohledů. Hierarchické dotazy dle ANSI SQL používají rekurzívní WITH klauzuli, která se odkazuje sama na sebe. Zavedení této syntaxe do Oracle 11gR2 zajišťuje hierarchickým dotazům Oracle SQL kompatibilitu s ANSI. ¨ Celou syntaxi si ukážeme na následujícím příkladu: WITH ORG_PRACOVNIKU (zamestnanec_id, prijmeni, jmeno, manazer_id, uroven) as ( SELECT zamestnanec_id, prijmeni, jmeno, manazer_id, 1 FROM A_HR.ZAMESTNANCI WHERE manazer_id is NULL -- začni od zaměstnance, který nemá nadřízeného UNION ALL -- spoj výsledek předchozího dotazu s výsledkem následujícího dotazu
SELECT pracovnici.zamestnanec_id , pracovnici.prijmeni, pracovnici.jmeno, pracovnici.manazer_id, manazeri.uroven+ 1 FROM A_HR.ZAMESTNANCI pracovnici join ORG_PRACOVNIKU manazeri on (manazeri.ZAMESTNANEC_ID = pracovnici.MANAZER_ID) -- podmínka spojení a rekurzívní volání ) SELECT * FROM ORG_PRACOVNIKU;
David Žák, Jiří Zechmeister IDAS2/12 – Pokročilé konstrukce SQL dotazů - část II
10
Pojmy k zapamatování Příkazy a funkce:
Klauzule WITH, CONNECT BY, CTE, hierarchické dotazy
Problém: dočasné pohledy, in-line pohledy, průchod stromovou strukturou, hierarchické dotazy Shrnutí V této lekci jste se seznámili s tvorbou SQL dotazů, které umožňují pracovat s dočasnými pohledy a hierarchickými strukturami. Ačkoli se mohou mírně lišit způsoby zápisu dočasných pohledů a hierarchických dotazů mezi jednotlivými databázovými platformami, seznámili jsme se s proprietárním řešením Oracle, které je přehlednější z hlediska zápisu, tak i řešením podle ANSI SQL, které se objevuje i v dalších databázových platformách a je univerzálnější.
Otázky na procvičení 1. 2. 3. 4. 5.
Vysvětlete, co jsou to in-line pohledy? Jak se definují dočasné pohledy v rámci SQL dotazu? K čemu slouží hierarchické dotazy? Uveďte obecnou syntaxi hierarchického dotazu s klauzulí CONNECT BY? Jak se tvoří hierarchické dotazy podle ANSI SQL.
Odkazy a další studijní prameny
http://www.techonthenet.com/oracle (syntaxe příkazů SQL jazyka a funkcí) http://www.oracle.com/technetwork/database/enterpriseedition/documentation (dokumentace k databázové platformě Oracle) http://technology.amis.nl/blog/6104/oracle-rdbms-11gr2-goodbyeconnect-by-or-the-end-of-hierarchical-querying-as-we-know-it
Odkazy a další studijní prameny
LACKO, L. Oracle, správa, programování a použití databázového systému. Praha: Computer Press, 2007. ISBN 80-251-1490-2. Hierarchical queries. Oracle® Database SQL Reference. [Online] Oracle, 2005. [Citace: 22. Duben 2011.] http://download.oracle.com/
David Žák, Jiří Zechmeister IDAS2/12 – Pokročilé konstrukce SQL dotazů - část II
11