|
| 1 | +# Relační Databáze |
| 2 | + |
| 3 | +Většina aplikací, zejména těch webových, potřebuje manipulovat s daty. |
| 4 | +Může se jednat třeba o správu objednávek, produktů na eshopu nebo bankovních transakcí. |
| 5 | + |
| 6 | +Vzhledem k tomu, že databáze jsou velice komplexní téma, tak si v této lekci |
| 7 | +projedeme jen základy. Pokud vás ale databáze zaujmou, tak je dobré se podívat |
| 8 | +po dalších materiálech, například na [W3Schools](https://www.w3schools.com/sql/). |
| 9 | + |
| 10 | +Nad daty provádíme čtyři druhy operací - vytváření, čtení, úpravu a mazání. |
| 11 | +Tyto operace se sdružují do zkratky **CRUD** (Create, Read, Update, Delete). |
| 12 | + |
| 13 | +Existují různé možnosti, kam data ukládat. Mohli bychom například vše zapisovat |
| 14 | +do jednoho souboru. Brzy bychom ale zjistili, že se jedná o velice neefektivní |
| 15 | +způsob - operace nad daty by s rostoucím počtem záznamů začaly být pomalé. |
| 16 | +V těchto situacích nastupují na scénu databáze (resp. databázové systémy). |
| 17 | + |
| 18 | +Jedná se o vysoce sofistikovaná řešení různých výrobců (zmínit můžeme třeba |
| 19 | +*Oracle* nebo *MySQL*). Výhodou je, že tyto systémy mají společný způsob, |
| 20 | +jakým se s nimi komunikuje (API) - prostřednictvím jazyka **SQL**. Nemusí nás tedy |
| 21 | +zajímat, jak databáze funguje uvnitř. |
| 22 | + |
| 23 | +Základem relačních databází jsou **tabulky**. Můžeme si je představit jako jeden |
| 24 | +list sešitu MS Excel (celý sešit by pak odpovídal databázi). Jednotlivé řádky |
| 25 | +tabulky jsou **záznamy**. Ty se skládají z několika **atributů** (sloupců). |
| 26 | +Řádky a sloupce odpovídají Excelovým řádkům a sloupcům. |
| 27 | + |
| 28 | +Můžeme mít například tabulku `OBJEDNAVKA`. Bude tvořena sloupci `ID_OBJEDNAVKY`, |
| 29 | +`ID_ZAKAZNIKA`, `DATUM`, `CENA`. Jednotlivé záznamy představují jednotlivé |
| 30 | +objednávky - víme tedy kdy si kdo udělal objednávku, a kolik za ni zaplatil. |
| 31 | + |
| 32 | +## SQL |
| 33 | +SQL (Structured Querying Language - Strukturovaný dotazovací jazyk) nám umožňuje |
| 34 | +komunikovat s databází. Výrobci jednolivých databází obvykle jazyk v některých |
| 35 | +oblastech rozšiřují, základ je ale společný a standardzivaný. Kromě práci s daty |
| 36 | +(CRUD) se jazyk používá pro vytváření tabulek a dalších objektů, pro správu |
| 37 | +uživatelů databáze apod. |
| 38 | + |
| 39 | +SQL příkazy mohou být do databáze posílány z webové aplikace (Python, Javascript, ...), |
| 40 | +z terminálu, nebo třeba z aplikace přímo určené pro správu databáze. |
| 41 | +Je zvykem jednotlivé příkazy jazyka psát `VELKÝMI PÍSMENY`. Příkazy oddělujeme |
| 42 | +středníkem `;`. Jednořádkové komentáře uvozujeme dvěma spojovníky `--` |
| 43 | +a víceřádkové komentáře píšeme mezi `/*` a `*/`. |
| 44 | + |
| 45 | +Pojďme se podívat na některé z příkazů, které SQL podporuje. |
| 46 | + |
| 47 | +### Tabulky |
| 48 | +Pro vytvoření tabulky použijeme příkaz `CREATE TABLE`, následovaný jménem tabulky |
| 49 | +a definicí jednotlivých sloupců. U sloupců definuje jméno, typ a případně další |
| 50 | +vlastnosti. |
| 51 | + |
| 52 | +```sql |
| 53 | +/* Vytvoří tabulku ROBOT se sloupci ID, NAME a TYPE. |
| 54 | + ID je celé číslo, NAME a TYPE jsou řetězce. |
| 55 | +*/ |
| 56 | +CREATE TABLE ROBOT ( |
| 57 | + ID INT PRIMARY KEY, |
| 58 | + NAME TEXT, |
| 59 | + TYPE TEXT |
| 60 | +) |
| 61 | +``` |
| 62 | + |
| 63 | +Význam `PRIMARY_KEY` si vysvětlíme později. Pokud bychom chtěli tabulku smazat, |
| 64 | +tak použijeme `DROP TABLE JMENO_TABULKY`. Jen pozor - spolu s tabulkou se |
| 65 | +**smažou všechny záznamy** v ní obsažené. |
| 66 | + |
| 67 | +### INSERT |
| 68 | +Jakmile máme vytvořenou tabulku, můžeme do ní vkládat záznamy. Děláme tak |
| 69 | +pomocí příkazu `INSERT`. Uvedeme jméno tabulky a pak hodnoty, představující |
| 70 | +záznam (nebo záznamy), které se mají vložit. |
| 71 | + |
| 72 | +```sql |
| 73 | +-- Vloží do tabulky ROBOT nového Robota s ID 1, jménem Jim a typem AGGRESIVE |
| 74 | +INSERT INTO ROBOT (ID, NAME, TYPE) VALUES (1, "Jim", "AGGRESSIVE"); |
| 75 | + |
| 76 | +-- Vloží do tabulky ROBOT další dva Roboty |
| 77 | +INSERT INTO ROBOT (ID, NAME, TYPE) VALUES (2, "John", "DEFENSIVE"),(3, "Jack", "DEFENSIVE"); |
| 78 | +``` |
| 79 | + |
| 80 | +### SELECT |
| 81 | +Když už máme i nějaké zaznámy, tak je dobré mít způsob, jak je číst. Slouží |
| 82 | +k tomu mocný příkaz `SELECT`, který v nejzákladnější podobě očekává seznam sloupců, |
| 83 | +který se má pro jednotlivé záznamy vypsat, a jméno tabulky, ze které se mají |
| 84 | +data číst. Seznam sloupců můžeme nahradit hvězdičkou `*`, pokud chceme vypsat |
| 85 | +hodnoty všech sloupců. |
| 86 | + |
| 87 | +```sql |
| 88 | +-- vypíše hodnoty všech sloupců pro všechny roboty |
| 89 | +SELECT * FROM ROBOT; |
| 90 | + |
| 91 | +-- vypíše jen jména všech robotů |
| 92 | +SELECT NAME FROM ROBOT; |
| 93 | +``` |
| 94 | + |
| 95 | +Příkaz `SELECT` podporuje třeba také filtrování dat (ukážeme si později), |
| 96 | +řazení (`ORDER BY`) a seskupování dat (průměr - `AVG`, suma - `SUM`, ...) |
| 97 | + |
| 98 | +### UPDATE |
| 99 | +V případě, že chceme data upravit (například změnit jméno robota), |
| 100 | +tak použijeme příkaz `UPDATE`. I ten očekává jméno tabulky, jejíž záznamy se |
| 101 | +mají upravit. Kromě toho také zadáme nové hodnoty pro jednotlivé sloupce. |
| 102 | +Hodnoty sloupců, které neuvedeme, zůstanou nezměněné. |
| 103 | + |
| 104 | +```sql |
| 105 | +-- nastaví všem robotům typ na AGGRESSIVE |
| 106 | +UPDATE ROBOT SET TYPE = "AGGRESSIVE"; |
| 107 | +``` |
| 108 | + |
| 109 | +### DELETE |
| 110 | +A konečně poslední oprací v CRUD je mazání. V SQL se záznamy mažou pomocí příkazu |
| 111 | +`DELETE`. I zde, stejně jako u ostatních CRUD příkazů, uvádíme jméno tabulky. |
| 112 | + |
| 113 | +```sql |
| 114 | +-- smaže všechny roboty |
| 115 | +DELETE FROM ROBOT; |
| 116 | +``` |
| 117 | + |
| 118 | +### WHERE |
| 119 | +Nejspíš vás napadalo, že pokud bychom vždy četli, upravovali nebo mazali všechna |
| 120 | +data v tabulce, tak by systém nebyl moc dobře použitelný. Naštěstí ale můžeme |
| 121 | +pomocí příkazu `WHERE` ovlivnit, nad jakými záznamy se bude operace provádět. |
| 122 | + |
| 123 | +Za `WHERE` se píší podmínky (spojované pomocí `AND` nebo `OR`), které musí |
| 124 | +platit, aby se záznam přidal do množiny, nad kterou se bude operace |
| 125 | +provádět. V podmínkách je možné použít různé operátory: |
| 126 | + |
| 127 | +- rovnost: `WHERE SLOUPEC = HODNOTA` |
| 128 | +- nerovnost: `WHERE SLOUPEC > HODNOTA` (`>`, `<`, `<>`, ...) |
| 129 | +- jedna z hodnot: `WHERE SLOUPEC IN (HODNOTA_1, HODNOTA_2)` |
| 130 | +- podřetězec: `WHERE SLOUPEC LIKE %HODNOTA%` |
| 131 | + |
| 132 | +```sql |
| 133 | +-- vypíše všechny řádky o robotech, které mají typ AGGRESSIVE |
| 134 | +SELECT * FROM ROBOT WHERE TYPE = "AGGRESSIVE"; |
| 135 | + |
| 136 | +-- přejmenuje roboty, jejichž jména začínají na J, na Jimmy |
| 137 | +UPDATE ROBOT SET NAME = "Jimmy" WHERE NAME LIKE "J%"; |
| 138 | + |
| 139 | +-- smaže robota s ID větším než 1 |
| 140 | +DELETE FROM ROBOT WHERE ID > 1; |
| 141 | +``` |
| 142 | + |
| 143 | +## Primární klíče |
| 144 | +Každá tabulka by měla mít soupec, který jednoznačně identifikuje jednotlivé řádky. |
| 145 | +Ve sloupci musí být unikátní hodnoty. Může se jednat o uměle vytvořené číslo |
| 146 | +(nejčastěji nazývané `ID`), nebo může jít o unikátní identifikátor z reálného |
| 147 | +světa. Pozor ale na to, že ne každý na první pohled unikátní identifikátor je |
| 148 | +*skutečně* unikátní - je například možné, aby dvě osoby měly stejné rodné číslo. |
| 149 | +Je tedy obecně lepší používat uměle vytvořené primární klíče. |
| 150 | + |
| 151 | +Primární klíče se používají proto, že zrychlují operace nad daty (rychle se podle |
| 152 | +nich vyhledává) a také proto, že snižují riziko duplicitních dat v databázi. |
| 153 | +Při vytváření tabulky zadáme u sloupce, který má být primárním klíčem, vlastnost |
| 154 | +`PRIMARY KEY`. |
| 155 | + |
| 156 | +## Cizí klíče |
| 157 | +Cizí klíče - `FOREIGN KEY` - se využívají pro zachycení vazby mezi tabulkami. |
| 158 | +V dceřiné tabulce máme sloupec představující cizí klíč, jehož hodnoty se |
| 159 | +odkazují na primární klíč jiné tabulky. |
| 160 | + |
| 161 | +Například tabulka `OBJEDNAVKA` má cizí klíč `ID_ZAKAZNIKA`. Tento cizí klíč |
| 162 | +se odkazuje na primární klíč tabulky `ZAKAZNIK`. Tím zaručíme, že každá objednávka |
| 163 | +musí být "napojená" na existujícího zákazníka. |
| 164 | + |
| 165 | +{{ figure( |
| 166 | + img=static('fk.png'), |
| 167 | + alt="Cizí klíče" |
| 168 | +) }} |
| 169 | + |
| 170 | +[zdroj obrázku](https://cdn-images-1.medium.com/max/1600/1*yW_ha3z8Mp6fUn9m6qWwNw.png) |
| 171 | + |
| 172 | +## Spojování tabulek |
| 173 | +Často potřebujeme v jednom dotazu číst data z více tabulek. Používáme k tomu |
| 174 | +příkaz `JOIN`, který je součástí příkazu `SELECT`. V základní verzi vezme |
| 175 | +`JOIN` záznamy z jedné tabulky a připojí k nim odpovídající záznamy z druhé |
| 176 | +tabulky. Na takto nově vzniklý řádek je možné udělat `JOIN` s další tabulkou. |
| 177 | + |
| 178 | +To, co považujeme za "odpovídající záznam" zapíšeme jako součást `JOIN` |
| 179 | +(za klíčové slovo `ON`). Nejdříve ale musíme zadat jméno tabulky, |
| 180 | +kterou chceme připojit. |
| 181 | + |
| 182 | +```sql |
| 183 | +-- vypíše jména a příjmení zákazníků, kteří udělali objednávku za více než 500 Kč |
| 184 | + |
| 185 | +SELECT ZAKAZNIK.JMENO, ZAKAZNIK.PRIJMENI |
| 186 | +FROM ZAKAZNIK |
| 187 | +JOIN OBJEDNAVKA ON OBJEDNAVKA.ZAKAZNIK_ID = ZAKAZNIK.ID |
| 188 | +WHERE OBJEDNAVKA.CENA > 500; |
| 189 | +``` |
| 190 | + |
| 191 | +## Transakce |
| 192 | +Transakce jsou skupiny příkazů (`SELECT`, `INSERT`, ...), která se buď provede |
| 193 | +celá, nebo vůbec. To má velkou výhodu v tom, že pokud některý z příkazů vyvolá |
| 194 | +chybu, tak se databáze vrátí do původního stavu, my můžeme příkaz opravit |
| 195 | +a celou transakci spustit znovu. |
| 196 | + |
| 197 | +O transakcích se ale bavíme především proto, že je důležité je **ukončit**, |
| 198 | +a to buď příkazem `COMMIT` nebo `ROLLBACK`. `COMMIT` uloží námi provedené |
| 199 | +změny trvale do databáze, takže je uvidíme i v budoucích transakcích. |
| 200 | +`ROLLBACK` vrátí databázi do původního stavu, což se může hodit v případě, |
| 201 | +že jsme provedli jiné změny, než jsme chtěli. |
| 202 | + |
| 203 | +```sql |
| 204 | +-- vložení dat |
| 205 | +INSERT INTO ROBOT (ID, NAME, TYPE) VALUES(1, "Jim", "AGGRESSIVE"); |
| 206 | +-- v této chvíli ještě nejsou data trvale uložena |
| 207 | + |
| 208 | +-- uložení dat |
| 209 | +COMMIT; |
| 210 | +``` |
| 211 | + |
| 212 | +## SQLite |
| 213 | +Celé povídání o databázích by nemělo moc smysl, pokud bychom si neukázali, |
| 214 | +jak je využít z prostředí Pythonu. Jak jsme už zmínili, existují různé databázové |
| 215 | +systémy, my se ale zaměříme na jeden z nejjednodušších na "rozjetí", a to **SQLite**. |
| 216 | + |
| 217 | +Tento systém ukládá celou databázi do jednoho binárního souboru. |
| 218 | +Můžeme si na něm jednoduše vyzkoušet jednotlivé SQL příkazy, |
| 219 | +a to prostřednictvím Python balíčku `sqlite3`. Ukažme si využití tohoto balíčku |
| 220 | +na příkladech. |
| 221 | + |
| 222 | +```sql |
| 223 | +import sqlite3 |
| 224 | + |
| 225 | +# Připojíme se k databázi (v souboru) |
| 226 | +connection = sqlite3.connect('pyladies_example_1.db') |
| 227 | + |
| 228 | +# Získáme instanci třídy `Cursor`, pomocí které bude do databáze posílat příkazy |
| 229 | +cursor = connection.cursor() |
| 230 | + |
| 231 | +# Pokud tabulka už existuje, tak ji odstraníme, |
| 232 | +# abychom mohli skript spouštět opakovaně |
| 233 | +cursor.execute("""DROP TABLE IF EXISTS ROBOT""") |
| 234 | + |
| 235 | +# Vytvoříme jednoduchou tabulku |
| 236 | +cursor.execute("""CREATE TABLE ROBOT (NAME TEXT, TYPE TEXT)""") |
| 237 | + |
| 238 | +# Vložíme do tabulky data |
| 239 | +cursor.execute(""" |
| 240 | + INSERT INTO ROBOT (NAME, TYPE) |
| 241 | + VALUES ("JIM", "DEFENSIVE"), ("JACK", "OFFENSIVE") |
| 242 | +""") |
| 243 | + |
| 244 | +# Dotážeme se na všechny roboty, výsledky vypíšeme |
| 245 | +robots = cursor.execute("SELECT * FROM ROBOT") |
| 246 | +for robot in robots: |
| 247 | + print(robot) |
| 248 | + |
| 249 | +# Uložíme změny a uzavřeme spojení |
| 250 | +connection.commit() |
| 251 | +connection.close() |
| 252 | +``` |
| 253 | + |
| 254 | +```sql |
| 255 | +# Složitejší příklad, který pracuje s primárními a cizími klíči |
| 256 | +# a se spojováním tabulek |
| 257 | + |
| 258 | +import sqlite3 |
| 259 | + |
| 260 | +# Připojíme se k databázi (v souboru) |
| 261 | +connection = sqlite3.connect('pyladies_example_2.db') |
| 262 | + |
| 263 | +# Získáme instanci třídy `Cursor`, pomocí které bude do databáze posílat příkazy |
| 264 | +cursor = connection.cursor() |
| 265 | + |
| 266 | +# Pokud tabulky už existují, tak ji odstraníme, |
| 267 | +# abychom mohli skript spouštět opakovaně |
| 268 | +cursor.execute("""DROP TABLE IF EXISTS ROBOT""") |
| 269 | +cursor.execute("""DROP TABLE IF EXISTS BATTLE""") |
| 270 | + |
| 271 | +# Vytvoříme tabulku s roboty a tabulky s výsledky bitev |
| 272 | +cursor.execute(""" |
| 273 | +-- u jednotlivých roborů si ukládáme ID, jméno a typ |
| 274 | +CREATE TABLE ROBOT ( |
| 275 | + ROBOT_ID INT PRIMARY KEY, |
| 276 | + NAME TEXT, |
| 277 | + TYPE TEXT) |
| 278 | +""") |
| 279 | + |
| 280 | +cursor.execute(""" |
| 281 | +-- bitva se skládá z ID bitvy, ID vítěze a poraženého (odpovídají ID v tabulce ROBOT) |
| 282 | +-- a z bodů pro vítěze a poraženého |
| 283 | +CREATE TABLE BATTLE ( |
| 284 | + BATTLE_ID INT PRIMARY KEY, |
| 285 | + WINNER_ID INT, |
| 286 | + LOSER_ID INT, |
| 287 | + WINNER_POINTS INT, |
| 288 | + LOSER_POINTS INT, |
| 289 | + FOREIGN KEY(WINNER_ID) REFERENCES ROBOT(ROBOT_ID), |
| 290 | + FOREIGN KEY(LOSER_ID) REFERENCES ROBOT(ROBOT_ID) |
| 291 | + ) |
| 292 | +""") |
| 293 | + |
| 294 | +# Vložíme do tabulkek data |
| 295 | +cursor.execute(""" |
| 296 | + INSERT INTO ROBOT (ROBOT_ID, NAME, TYPE) VALUES |
| 297 | + (1, "JIM", "DEFENSIVE"), (2, "JACK", "OFFENSIVE"), (3, "JIMMY", "OFFESIVE") |
| 298 | +""") |
| 299 | + |
| 300 | +cursor.execute(""" |
| 301 | + INSERT INTO BATTLE (BATTLE_ID, WINNER_ID, LOSER_ID, WINNER_POINTS, LOSER_POINTS) VALUES |
| 302 | + (1, 1, 2, 10, 8), -- robot 1 porazil robota 2 se skóre 10:8 (v bitvě 1) |
| 303 | + (2, 2, 1, 6, 9), |
| 304 | + (3, 2, 3, 10, 9), |
| 305 | + (4, 1, 3, 5, 4), |
| 306 | + (5, 3, 2, 2, 0), |
| 307 | + (6, 1, 2, 9, 6) |
| 308 | +""") |
| 309 | + |
| 310 | +# Dotážeme se na výsledky bitev, které vyhrál robot se jménem "JIM" |
| 311 | +scores = cursor.execute(""" |
| 312 | + SELECT BATTLE.WINNER_POINTS, BATTLE.LOSER_POINTS |
| 313 | + FROM BATTLE |
| 314 | + JOIN ROBOT ON ROBOT.ROBOT_ID = BATTLE.WINNER_ID |
| 315 | + WHERE ROBOT.NAME = "JIM" |
| 316 | +""") |
| 317 | + |
| 318 | +for score in scores: |
| 319 | + print(score) |
| 320 | + |
| 321 | +# Uložíme změny a uzavřeme spojení |
| 322 | +connection.commit() |
| 323 | +connection.close() |
| 324 | +``` |
| 325 | + |
| 326 | +## ORM |
| 327 | +V Pythonu jsme se naučili data a logiku sdružovat do **tříd**. V databázích |
| 328 | +se data sdružují do **tabulek**. O propojení těchto konceptů se stará ORM - Objektově |
| 329 | +Relační Mapování. Pomocí ORM Frameworku (v Pythonu např. |
| 330 | +[SQLAlchemy](https://en.wikipedia.org/wiki/SQLAlchemy)) vytváříme Python třídy, |
| 331 | +pro které existují odpovídající tabulky v databázi. |
| 332 | + |
| 333 | +Například Python třída `Kocka` bude mít odpovídající tabulku `KOCKA`. |
| 334 | +Atributy třídy (`Vek`, `Barva`) budou v databázi existovat jako sloupce. |
| 335 | +Jednotlivé řádky tabulky bude možné načíst do aplikace jako instance třídy `Kocka`. |
| 336 | +Jednotlivé kočky samozřejmě bude možné upravovat, mazat, nebo vytvářet nové. |
| 337 | + |
| 338 | +Ukázku ORM najdete na [Wikipedii](https://en.wikipedia.org/wiki/SQLAlchemy#Schema_definition) |
0 commit comments