Každý databázové prostředí pracuje s tabulkami, v tomto případě je to základní "úložiště dat". Podobnost s tabulkami, jak je známe z tabulkových kalkulátorů není náhodná, v našem případě se ale kladou na jisté aspekty práce mimořádné nároky, pro které programy typu Excel nejsou určeny. Právě proto je představa databáze jakožto jediné tabulky mylná, jedná se o soustavu tabulek s logickými vazbami. Vytvořme si nyní pro demonstraci fiktivní školu a její zaměstnance.
Relace
Promítneme-li si popisovanou firmu do reálné situace v Excelu, zjistíme, že data by byla organizována jinak. Byla by pouze jediná tabulka se třemi sloupci a jména jednotlivých předmětů by se opakovala na jednotlivých řádcích. Zde je mezi sloupci UČITELÉ.Předmět a PŘEDMĚTY.Číslo relace. Je to logická vazba, říkající, že hodnota ve sloupci Předmět značí číslo řádku v tabulce PŘEDMĚTY (zde se názvosloví teorie trochu liší s populárně naučnou literaturou, k čemuž se ještě vrátím). Tím je například pan Durych jednoznačně svázán s předmětem Matematika (stejně jako pan Adámek), paní Červená s dějepisem apod. Co ale případ zaměstnankyně Hájkové? NULL v databázích představuje jakési "prázdno", sloupec v této řádce nenabývá žádné hodnoty a paní Hájková tedy žádný předmět nevyučuje. Žádná anomálie není také čtvrtý řádek v tabulce PŘEDMĚTY, fyziku prostě nikdo nevyučuje. Abychom pochopili relační model úplně, je potřeba k tomuto názornému příkladu "dobalit" trochu důležité teorie.
Jak je z popisovaného principu patrné, každý zaměstnanec může učit pouze jeden předmět, ale každý předmět může být vyučován více lidmi. Takovýto vztah označujeme jako 1:N. Vedle toho existují ještě dvě další formální označení, jednodušší 1:1 a složitější N:M. Prvně jmenovanému vztahu odpovídá situace dvou tabulek, ve kterých jsou vždy dva řádky jednoznačně svázány. Uvažme například tabulku TŘÍDY se sloupcem Třídní_učitel. Není možné, aby jedna třída měla třídních učitelů více a každý může být třídní učitel maximálně jedné třídy. Naopak vztah M:N popisuje přesně opačnou situaci. Zkusme poopravit původní dvě tabulky tak, aby každý vyučující mohl učit více předmětů. Někomu by se mohlo zdát, že nejde na náš relační model aplikovat, ale není to pravda. Musíme akorát použít malý trik, kterým spojení dvou relací 1:N v nové tabulce VYUČUJE. Pokud nebude uvedeno jinak, tak se v textu se nadále budu odvolávat vždy na poslední uvedený příklad.
Tabulka UČITELÉ :
Číslo Jméno Plat Narození Hodin
1 Durych 8000 1954-11-05 35
2 Adámek 8000 1967-05-03 32
3 Liborec 11000 1980-09-28 38
4 Červená 9000 1971-10-10 29
5 Hájková 10500 1979-01-13 39
Tabulka PŘEDMĚTY:
Číslo Popis
1 Matematika
2 Zeměpis
3 Dějepis
4 Fyzika
Tabulka VYUČUJE:
Učitel Předmět
1 1
1 4
2 1
3 2
4 3
4 1
Tabulka VYUČUJE je zde nedílná spojovací část. Určuje, že učitel s číslem 1 učí předměty 1 a 4, učitel č. 2 učí předmět č.3, č. 3 učí předmět č.2 a č. 4 učí předměty č.3 a č.1. Pro takovýto popis mezi vztahy tabulek se používají identifikátory, jejichž účelem je jednoznačně identifikovat daný řádek tabulky. Jeho hodnota proto v rámci jedné tabulky musí být jedinečná, nesmí se opakovat. Nejčastěji to bývají čísla - v databázích se totiž dá nastavit automatické zvyšování a doplňování hodnoty. Při přijmutí nového kantora by tak systém automaticky doplnil hodnotu 5 do identifikátoru číslo v tabulce UČITELÉ. Jednoznačný a neměnný identifikátor, který má navíc nejkratší délku, nazýváme primární klíč. Pokud není žádný sloupec unikátní, může být primární klíč také tzv. složený, kdy ho jakoby tvoří více sloupců najednou. Použití primárního klíče v cizí tabulce k vytvoření vztahu nazýváme cizí klíč. V prvním příkladu je například v tabulce učitelů sloupec Číslo klíčem primárním, zatímco sloupec Předmět klíčem cizím.
Příkaz SELECT
Příkaz SELECT je jistě nejdůležitější částí jazyka SQL, v knihách o databázích obvykle také zabírá většinu prostoru. Výsledkem vykonání tohoto příkazu je tabulka všech řádků, která vyhovují daným kritériím. Ta se poté předá programu, který dotaz vydal (například PHP). Vlastní syntaxe má několik částí, ze kterých jsou povinné pouze dvě. Nejjednodušší příklad může vypadat například takto :
SELECT * FROM UČITELÉ
Zde jsme tedy použili části SELECT a FROM. Hvězdička zde zastupuje všechny sloupce tabulky, jak je z asi anglické terminologie patrno, vybere se vše z tabulky učitelů. Výsledná tabulka, kterou takovýto dotaz vyprodukuje se tedy bude shodovat s tabulkou učitelů. Daleko větší síla příkazu je ale v ostatních částech. Uveďme si proto nyní příklad další.
SELECT Jméno, Plat FROM UČITELÉ WHERE Plat<10000 ORDER BY Jméno
Nejlepší vysvětlení mnoha SQL příkladů je často jejich "překlad" do češtiny. Tento kupříkladu říká "vyber sloupce Jméno a Plat z tabulky UČITELÉ a to pouze ty, kde Plat je menší než 10000, výsledek seřaď (vzestupně) podle jména (tedy abecedně). Výsledná tabulka bude tedy vypadat takto :
Jméno Plat
Adámek 8000
Červená 9000
Durych 8000
Tím ale nekončí ani základní možnosti příkazu SELECT. Je nutné podotknout, že v částech WHERE a ORDER BY mohou být daleko delší výrazy. Tak například chceme vybrat všechny zaměstnance, kteří se narodili před rokem 1970 a mají plat menší než 10 tisíc. Vedle toho ještě chceme zjistit jejich hodinovou sazbu zaokrouhlenou na celá čísla. Výsledek chceme seřadit podle platu vzestupně a v případě shody podle data narození sestupně. Na dotaz
SELECT Jméno, ROUND(Plat/Hodin) AS Na_Hodinu FROM UČITELÉ WHERE (Plat<10000) AND (Narození<'1970-01-01') ORDER BY Plat, Narození DESC
odpoví databázový stroj následující tabulkou:
Jméno Na_Hodinu
Adámek 250
Durych 288
Červená 310
Z tohoto dotazu jsme zjistili tři nové poznatky. Jednak v části WHERE může být jakkoliv dlouhý logický výrok, ve kterém je možno používat spojky jako AND, OR, XOR apod., dále řadit se dá podle několika sloupců a to sestupně i vzestupně (všimněte si, že datový typ datum má syntaxi YYYY-MM-DD, aby se urychlilo řazení) a nakonec si můžeme v čísti SELECT vytvořit nový sloupec z výsledků jakéhokoliv matematického výrazu.
Spojování tabulek
Až do teď se každý dotaz týkal pouze jediné tabulky. V SQL je možné tabulky libovolně spojovat a konstruovat tak daleko složitější dotazy. Co když například chceme zjistit, který vyučující učí jaký předmět? Je nutné spojit všechny tři tabulky. Spojení v databázovém slova smyslu je totéž co kartézský součin. Například Kartézský součin množiny M={a; b; c} a N={1; 2} je roven množině K={(a;1); (a;2); (b;1); (b;2); (c;1); (c;2)}, jedná se tedy o soubor všech možných kombinací prvků z obou množin. Podobným způsobem funguje spojování tabulek, prvky jednotlivých množin jsou řádky tabulek. Výsledný součin pro tabulky s k, l a m řádky může mít tedy maximálně k*l*m řádků. Ukažme si část součinu UČITELÉ x PŘEDMETY x VYUČUJE (byly vyřazeny irelevantní sloupce Plat, Narození a Hodin)
UČITELÉ VYUČUJE PŘEDMĚTY
Číslo Jméno Učitel Předmět Číslo Popis
----------------------------------------------------
1 Durych 1 1 1 Matematika
1 Durych 1 4 2 Zeměpis
... ... ... ... ... ...
... ... ... ... ... ...
2 Adámek 2 1 1 Matematika
2 Adámek 2 1 2 Zeměpis
2 Adámek 2 1 3 Dějepis
2 Adámek 2 1 4 Fyzika
2 Adámek 3 2 1 Matematika
... ... ... ... ... ...
... ... ... ... ... ...
4 Červená 3 2 2 Zeměpis
4 Červená 4 3 3 Dějepis
4 Červená 4 1 1 Matematika
... ... ... ... ... ...
... ... ... ... ... ...
Jak je vidět, v tabulce je mnoho irelevantních součinů, neboť z povahy relace je nutné, aby se rovnala jednak položka UČITELÉ.Číslo s položkou VYUČUJE.Učitel, jednak položka VYUČUJE.Předmět s položkou PŘEDMĚTY.Číslo. Přes tyto sloupce tabulky spojíme. Ostatní řádky tedy nevyhovují reálné situaci a správný SQL dotaz (na jméno vyučujícího a název předmětu) vypadá takto :
SELECT Jméno, Popis FROM UČITELÉ, VYUČUJE, PŘEDMĚTY WHERE (UČITELÉ.Číslo=VYUČUJE.Učitel) AND (VYUČUJE.Předmět=PŘEDMĚTY.Číslo) ORDER BY Jméno
Systém by měl vrátit správné přiřazení jednotlivých předmětů k vyučujícím profesorům.
Jméno Popis
Adámek Matematika
Červená Dějepis
Durych Matematika
Durych Fyzika
Liborec Zeměpis
Speciálně při spojování tabulek je nutné brát v ohled určitou kolizi názvů sloupců, kdy dvě tabulky například obsahují sloupec Číslo. V části SELECT musíme při explicitním výběru určit, o jakou tabulku jde, tedy např. SELECT UČITELÉ.Číslo FROM UČITELÉ, PŘEDMĚTY. V případě použití hvězdičky (*) je nutné sporné sloupce explicitně přejmenovat pomocí klauzule AS (SELECT *, UČITELÉ.Číslo AS CisloUcitele ... ). Obdobně musíme postupovat v části WHERE, aby stroj věděl, který sloupec máme na mysli.
Další typická situace, která může nastat, je spojení jedné tabulky s tou samou. To použijeme v případě, že se jeden sloupec odkazuje na jiný řádek té samé tabulky. Výsledný součin tak bude mít maximálně m*m řádek, kde m je počet řádků tabulky původní. Uvažme například situaci tabulky ZAMĚSTNANCI, která má pole Číslo (integer), Jméno (string) a Nadřízený (integer). Pokud chceme získat seznam všech pracovníků se jmény jejich nadřízených, musíme ale dvojí použití stejné tabulky oddělit, tedy v části FROM oddělíme a jinak pojmenujeme pracovníky a jejich nadřízené. Použijeme následující dotaz:
SELECT PODRIZENI.Jméno AS Jméno, SEFOVE.Jméno AS Šéf FROM ZAMĚSTNANCI PODRIZENI, ZAMĚSTNANCI SEFOVE WHERE PODRIZENI.Nadřízený=SEFOVE.Číslo
Agregační funkce a seskupování výsledků
Jazyk SQL podporuje velmi široké spektrum různých souhrnných funkcí, které se často používají v praxi. Místo dlouhého výkladu si uveďme jeden dotaz, který zodpoví několik otázek najednou.
SELECT SUM(Plat), MIN(Plat), MAX(PLAT), AVG(Plat) FROM UČITELÉ
Takovýto příkaz vybere celkové platové náklady, minimální a maximální plat, průměrný plat a to v tomto pořadí (do průměrné hodnoty funkce AVG se nezapočítávají hodnoty NULL). Další velmi často používanou funkcí je COUNT(), která vrací počet řádků v daném sloupci. Celkový počet všech učitelů školy tak zjistíme dotazem SELECT Count(Číslo) FROM UČITELÉ (jako počet unikátních Čísel učitelů).
Tyto agregační funkce mají podobně jako příkaz SELECT svoje modifikátory a opět je implicitně použit ALL. Modifikátor *, tedy například COUNT(* Předmět) znamená, že do výsledku bude započítáváno vše, tedy i řádky s hodnotou NULL. Modifikátor DISTICT má obdobnou funkci jako u příkazu SELECT.
Častá náplň práce s databází je získávání souhrnných údajů určitých skupin, k čemuž se používá část GROUP BY [výraz]. Pro každý řádek výpisu se [výraz] vyhodnotí a stejné výsledky se jakoby srazí dohromady. Uvažme například dotaz na počet učitelů jednotlivých předmětů, zde skupiny vytvoříme podle stejných čísel učitelů. Nejprve spojíme dvě tabulky dohromady, obdobně jako v jednom z předešlých příkladů. Následně spočítáme počet všech unikátních učitelů pomocí funkce COUNT().
SELECT Popis, COUNT(VYUČUJE.Učitel) FROM VYUČUJE, PŘEDMĚTY WHERE (VYUČUJE.Předmět=PŘEDMĚTY.Číslo) GROUP BY VYUČUJE.Učitel
Systém by měl odpovědět tabulkou jednotlivých předmětů a počtem učitelů, kteří je učí. S použitím GROUP BY se často objevuje klauzule HAVING, která specifikuje další podmínku, kterou musí výběr splňovat. Její význam je obdobný části WHERE ovšem s tím rozdílem, že při seskupování podmínka HAVING omezuje jakoby již hotová seskupeni, kdyžto WHERE omezuje jednotlivé řádky ještě před jejich zařazením do skupin.
Stromová struktura v databázi
Příklad s nadřízenými zaměstnanci popisuje situaci, kdy některé objekty jsou podřízené jiným. Uvažme, zda je možné totéž udělat pro větší hierarchickou hloubku. Spojování tabulek nám přestává sloužit, už jenom proto, že počet řádků součinu exponenciálně narůstá a pro hloubku n je roven k^n, kde k je počet řádků původní tabulky. Zde se velmi často používá tzv. omezení intervalem, přestože má své podstatné nevýhody, není totiž možné stromovou strukturu stavit do nekonečna "nahoru" (tedy je omezená hloubka) ani "do stran", tedy pod jeden objekt může patřit maximálně konečný počet objektů jiných. Zkusme například jednou tabulkou popsat adresářovou strukturu disku. Pro úplné zjednodušení položme maximální hloubku adresáře jako 5 a maximální počet adresářů v jednom podadresáři na 9. Tabulka STRUKTURA bude mít v našem případě dva sloupce : Jméno (string) a Hierarchie (integer s implicitní hodnotou 100000). Nejprve vložíme domovský adresář s přednastaveným číslem hierarchie (déle jen číslo).
Dále, pokud chceme přidat složku pod tu domovskou (s číslem 100000), postupujeme následovně. Deset podložek na této úrovni může nabývat čísel od 11...,12..,13... do 19... Tedy nejprve si zjistíme jejich počet (SELECT Count(Číslo) from FROM STRUKTURA GROUP BY Číslo) a k němu přičteme jednu. Poté vezmeme číslo složky pod kterou chceme přidávat a první nenulovou číslici zleva nahradíme takto vypočteným číslem. Výsledek s novou hodnotou zapíšeme do databáze. Pokud se vám zdá celý mechanizmus složitý, podívejte se, jak po naplnění tabulky několika názornými řádky bude vypadat výpis SELECT * FROM STRUKTURA ORDER BY Hierarchie.
Jméno Hierarchie
---------------------------
Home 100000
Software 110000
Windows 120000
Office 121000
Word 121100
Excel 121200
Data 120000
Dopisy 121000
Petr 121100
Michal 121200
Jak je vidět, šestý znak zleva (jednička) v implicitní hodnotě zajišťuje zachování potřebné délky. Pro vypsání celého stromu struktury nyní stačí všechny složky seřadit vzestupně podle hierarchického čísla. Pokud chceme výpis začít například od složky Dopisy, musíme si zjistit mezní hodnoty. Ta menší, A, je číslo počáteční složky, ta větší je pak číslo A, ve kterém je první nenulová číslice zleva zvětšena o 1 (s přenosem na další řád v případě 9+1=10). V případě dopisů by stačilo zadat dotaz:
SELECT * FROM STRUKTURA WHERE (Hierarchy>=120000) AND (Hierarchy<130000) ORDER BY Hierarchy
Jediný problém tohoto schématu je v případě odebírání prvků, respektive při následujícím přidávání. Pokud bychom odebrali složku Word, tak by následně přidaná složka Access měla číslo 121300, čímž bychom mohli deset volných míst zanedlouho vyčerpat. Proto je nutné při přidávání brát jakékoliv nepoužité číslo, nikoliv to "další", jak bylo v úvodu pro zjednodušení uvedeno.
Z vlastní programátorské praxe mohu potvrdit úspěšné použití této techniky, například v redakčním systému ve stromové struktuře komentářů k článkům. Poté, co jsem složité rekurzivní vypisování nahradil tímto efektivním způsobem, se práce se články podstatně zrychlila a skripty mají podstatně menší délku. Tato technika lze navíc použít i na velmi velká čísla, Hierarchy má v mém případě délku 38 míst a indexy v jednotlivých složkách jsou po třech číslicích (tedy max. 999 podložek).
Složené dotazy
Za některých podmínek je možné spojit dva dotazy v jeden. Při takovýchto operacích je především nutné, aby se jednotlivé sloupce shodovali typově i co do počtu. Pro tyto účely jsou v SQL MNOŽINOVÉ množinové operátory. Nejjednodušším z nich je UNION, který zastupuje prosté sjednocení množin, včetně dodatku, že duplicitní záznamy se ve sjednocení berou jako jeden prvek. Pokud bychom chtěli toto odstranit, použijeme operátor UNION ALL. Pokud bychom měli například v naší imaginární škole v oddělených tabulkách učitele a ostatní pracující, seznam všech zaměstnanců získáme následujícím dotazem :
SELECT Jméno FROM UČITELÉ UNION SELECT Jméno FROM OSTATNÍ_ZAM
Obdobným způsobem (pochopitelně ovšem v jiné situaci) lze použít operátor INTERSECT pro průnik množin (tabulek) nebo MINUS (někdy EXCEPT), při kterém jsou do výsledku zařazeny výsledky z prvního dotazu, které se nevyskytly v dotazu druhém.