Zarábaj až 6 000 € mesačne! Akreditované rekvalifikačné kurzy od 0 €. Viac informácií.

23. diel - Reštrikcie v databáze SQL - ON DELETE a ON UPDATE

V predchádzajúcom cvičení, Riešené úlohy k 21.-22. lekciu MySQL/MariaDB , sme si precvičili získané skúsenosti z predchádzajúcich lekcií.

V tutoriáli si probereme reštrikcie, ktoré sú významným pomocníkom pre automatizáciu procesov nad databázou. Ukážeme si, ako fungujú a ako ich používať.

Možno s databázami ešte len začínate, možno ich používate už niekoľko mesiacov či rokov a možno v rámci aplikácií pravidelne riešite otázky typu: "Chcem zmazať tento záznam, s čím všetkým je prepojený?", "Ako zmazanie ovplyvní previazanosť databázy?", "Môžem záznam zmazať rovno, alebo musím vopred odobrať to a tamto?", a "Musím na to stále myslieť?“. Nemusíte! Ukážeme si, ako možno pomocou reštrikcií jednoducho databázu prinútiť, aby tieto otázky riešila za nás. A nebojte sa, nie je to žiadna veda.

ON DELETE CASCADE

Predstavme si vzorovú situáciu, kedy máme v databáze dve tabuľky - tabuľku budovy a tabuľku miestnosti:

  • Každá budova má ľubovoľný počet miestností.
  • Každá miestnosť patrí jednej budove - miestnosť bez budovy nemôže existovať.
  • Priradenie miestnosti k budove sa vykonáva pomocou definície cudzích kľúčov.
  • Tabuľky sú vo vzťahu 1:N.
  • Pri zmazaní miestnosti sa nič nedeje.
  • Pri zmazaní budovy musí dôjsť aj k zmazaniu miestností.

Pre funkčnosť príkladu je nutné zmazať všetky miestnosti patriace do budovy a až potom zmazať samotnú budovu. Na toto treba vždy myslieť a strážiť to. V rámci lekcie ide o jednoduchý príklad, avšak v reálnom projekte môže byť previazanie jednotlivých záznamov oveľa komplexnejšie. Napr. zošrotovaním auta dôjde k odpisu nielen auta ako takého, ale aj jeho súčastí – motora, prevodovky, kolies, ai. Motor môže ďalej obsahovať odkazy na ďalšie súčiastky, ktoré musia byť zmazané, atď.

Mazanie bez použitia reštrikcií

Mazanie bez reštrikcií by sme napísali nejako takto. V SQL databáze by sme mohli mať:

DELETE FROM `rooms` WHERE `building_id` = 2;
DELETE FROM `buildings` WHERE `id` = 2;

Použitie reštrikcie ON DELETE CASCADE

S komplexnosťou previazania však rastie množstvo vynaloženého úsilia na vytváranie ďalších a ďalších mazacích dotazov na databázu. Riešenie zložitosti tejto situácie je použitie reštrikcie ON DELETE CASCADE, ktorá sa definuje na cudzom kľúči v podriadenej tabuľke rooms a zaisťuje, že pokiaľ dôjde k zmazaniu záznamu v nadradenej tabuľke buildings, dôjde aj k zmazaniu všetkých záznamov príslušných danej budove v tabuľke rooms.

V SQL databáze by sme mali:

CREATE TABLE `rooms` (
   `id` INT PRIMARY KEY AUTO_INCREMENT,
   `building_id` INT NOT NULL
);
ALTER TABLE `rooms` ADD FOREIGN KEY (`building_id`) REFERENCES `buildings` (`id`) ON DELETE CASCADE;

Týmto je teraz zaistené, že zmazaním budovy dôjde k automatickému zmazaniu všetkých príslušných miestností.

Môžeme si vyskúšať v SQL:

DELETE FROM `buildings` WHERE `id` = 2;

Zistenie reštrikcie

V databáze je možné pre požadovanú tabuľku jednoducho zistiť, ktoré podriadené tabuľky majú túto reštrikciu nastavenú.

Pre zistenie reštrikcie vložíme SQL dotaz nad databázu information_schema:

SELECT
   table_name
FROM
   information_schema.referential_constraints
WHERE
   constraint_schema = 'database_name'
   AND delete_rule = 'CASCADE';

ON DELETE RESTRICT (ON DELETE NO ACTION)

V MySQL/MariaDB ide o ekvivalentné reštrikcie. V iných databázach (PostgreSQL, SQLite, …) vychádzajúcich z SQL 2003 štandardu je medzi nimi rozdiel v tom, že sa RESTRICT kontroluje pred akoukoľvek ďalšou operáciou, a NO ACTION sa vykonáva až po vykonaní operácií, ktorými môžu byť napr. spúšťače. Databáza Oracle RESTRICT nepoužíva.

Pri MySQL/MariaDB je RESTRICT v skutočnosti východiskovým nastavením pre definíciu cudzieho kľúča bez explicitného zadania reštrikcií.

Nastavením tejto reštrikcie na cudzí kľúč je zakázané zmazanie záznamu nadradenej tabuľky buildings, pokiaľ má priradené nejaké miestnosti.

Keby sme v SQL mali napr:

CREATE TABLE `rooms` (
   `id` INT PRIMARY KEY AUTO_INCREMENT,
   `building_id` INT NOT NULL
);
ALTER TABLE `rooms` ADD FOREIGN KEY (`building_id`) REFERENCES `buildings` (`id`) ON DELETE RESTRICT;

Pri pokuse o zmazanie budovy, ktorá obsahuje miestnosti sa vyhodí chybové hlásenie o tom, že takúto operáciu nemožno vykonať.

Pri mazaní z SQL:

DELETE FROM `buildings` WHERE `id` = 2;

Vyvolá výnimku: Cannot delete or update a parent row: a foreign key constraint fails (testdb.rooms, CONSTRAINT rooms_building_id_foreign FOREIGN KEY (building_id) REFERENCES `buildings (id))

ON DELETE SET NULL

V prípade, že podriadené záznamy zmazať nechceme, je možné nastaviť cudzí kľúč pri zmazaní nadradeného záznamu na NULL. Urobili by sme to napríklad v prípade, že máme v izbách mazané budovy nábytok, o ktorom vieme, že ho nechceme zničiť, ale vysťahovať niekam pred budovu. U cudzieho kľúča je dôležité na túto možnosť myslieť a nenastavovať mu obmedzenia NOT NULL:

CREATE TABLE `furniture` (
   `id` INT PRIMARY KEY AUTO_INCREMENT,
   `room_id` INT N̶O̶T̶ ̶N̶U̶L̶L̶
);
ALTER TABLE `furniture` ADD FOREIGN KEY (`room_id`) REFERENCES `rooms` (`id`) ON DELETE SET NULL;

Pri zmazaní miestnosti dôjde k "uvoľneniu nábytku". Ten zostane zachovaný, ale už nebude priradený k žiadnej miestnosti.

ON DELETE SET DEFAULT

Predvolená hodnota záleží na nastavení daného stĺpca pre daný cudzí kľúč. Pokiaľ by napríklad budova mala skladovú miestnosť s id miestnosti 1, potom nastavením DEFAULT hodnoty na 1 a nastavením reštrikcií na SET DEFAULT spôsobí to, že po zmazaní inej miestnosti danej budovy sa jej nábytok automaticky presunie do jej skladu.

Nie všetky databázové enginy SET DEFAULT podporujú! Napríklad často používaný InnoDB, alebo menej známy NDB zadanie týchto reštrikcií spracujú, ale ignorujú ich. Sú nahradené za RESTRICT.

Táto reštrikcia má ale svoje obmedzenia (podľa popísaného príkladu):

  • Pri zmazaní miestnosti v akejkoľvek budove sa prevedie všetok nábytok do miestnosti s id = 1. Nie je teda možné nastaviť niečo ako skladovú miestnosť pre každú budovu zvlášť.
  • Pri vytváraní tohto cudzieho kľúča už musí existovať záznam s id = 1 v tabuľke miestností.

Zhrnutie ON DELETE reštrikcií

Zhrnieme si ON DELETE reštrikcie:

CASCADE Prebublanie informácie o zmazaní nadriadeného záznamu a zmazaní i záznamov jemu podriadených.
RESTRICT/NO ACTION Je zakázané zmazať nadriadený záznam, pokiaľ existujú záznamy jemu podriadené.
SET NULL Pri mazaní nadradeného záznamu je mu podriadeným záznamom odobrané prepojenie na tento záznam a samotný záznam je zmazaný.
SET DEFAULT Túto reštrikciu nie je odporúčané používať mimo veľmi špecifických prípadov a u databáz, ktoré toto podporujú. Odporúča sa nahrádzať za SET NULL.

ON UPDATE vs. ON DELETE

Reštrikcia ON DELETE za nás stráži, čo sa deje s podriadenými záznamami v prípade, keď dôjde k zmazaniu záznamu im nadradenému. SQL stroj stráži cudzie kľúče a v prípade existencie odkazu na práve mazaný primárny kľúč vykoná operáciu podľa nastavenia reštrikcie. ON UPDATE je svojou funkčnosťou veľmi podobný, len s tým rozdielom, že nestráži dotazy na mazanie primárnych kľúčov, ale stráži dotazy na ich modifikáciu.

ON UPDATE CASCADE

Podobne ako pri reštrikcii ON DELETE, kaskáda tu znamená prebublanie zmeny (v tomto prípade napríklad prečíslovanie primárneho kľúča) nadradeného záznamu na záznamy podriadené. V praxi to vyzerá napríklad tak, že ak záznamu, na ktorý sa odkazujú záznamy iných tabuliek zmeníme primárny kľúč, je tento primárny kľúč zmenený aj pri všetkých záznamoch v odkazujúcich tabuľkách:

CREATE TABLE `rooms` (
  `id` INT PRIMARY KEY AUTO_INCREMENT,
  `building_id` INT NOT NULL
);
ALTER TABLE `rooms` ADD FOREIGN KEY (`building_id`) REFERENCES `buildings` (`id`) ON UPDATE CASCADE;

Doteraz som sa v praxi nestretol s prípadom, kedy by bolo treba meniť identifikátor databázového záznamu. Pokiaľ k tomu ale nejaký dôvod existuje, zmeníme napr. building_id z 2 na 5:

UPDATE `buildings` SET `id` = 5 WHERE `id` = 2

Zmena sa aplikuje aj na všetky miestnosti patriace budove 2, čiže na záznamy podriadené tomuto záznamu.

ON UPDATE RESTRICT (ON UPDATE NO ACTION)

Rozdiel medzi RESTRICT a NO ACTION sme si už popísali v časti o reštrikcii ON DELETE. Aj tu sa riadi zmena nadradeného záznamu tým, či existujú záznamy jemu podriadené. Pokiaľ napr. existujú rooms priradené budove, tak tejto budove nemožno upraviť jej primárny kľúč:

CREATE TABLE `rooms` (
   `id` INT PRIMARY KEY AUTO_INCREMENT,
   `building_id` INT NOT NULL
);
ALTER TABLE `rooms` ADD FOREIGN KEY (`building_id`) REFERENCES `buildings` (`id`) ON UPDATE RESTRICT;

ON UPDATE SET NULL

Pri zmene primárneho kľúča nadradeného záznamu dôjde k uvoľneniu jemu podriadených záznamov – hodnota cudzieho kľúča je nastavená na NULL. Opäť ako pri ON DELETE je aj tu potrebné na toto myslieť pri definícii cudzieho kľúča a nezabudnúť nezadávať mu NOT NULL vlastnosť. Pokiaľ teda nejaké rooms zmeníme primárny kľúč, dôjde k vysťahovaniu všetkého nábytku na ulici:

CREATE TABLE `rooms` (
   `id` INT PRIMARY KEY AUTO_INCREMENT,
   `building_id` INT NOT NULL
);
CREATE TABLE `furniture` (
   `id` INT PRIMARY KEY AUTO_INCREMENT,
   `room_id` INT N̶O̶T̶ ̶N̶U̶L̶L̶
);
ALTER TABLE `furniture` ADD FOREIGN KEY (`room_id`) REFERENCES `rooms` (`id`) ON UPDATE SET NULL;

ON UPDATE SET DEFAULT

Rovnako ako sme si popísali pri reštrikcii ON DELETE, ani ON UPDATE SET DEFAULT nie je vhodné pre väčšinu bežných prípadov.

Slovo na záver

K napísaniu tejto lekcie ma viedli vlastné skúsenosti z viacerých projektov. Až ťažko uveriteľne často sa stáva, že tak základný stavebný prvok, akým sú reštrikcie sa v databázach nepoužívajú. Možno ani nie tak preto, že by vývojári boli leniví, ale ako to určite tiež poznáte, jednoducho nie je čas, a presunie sa to "na neskôr". Alebo sa s tým pri projekte začne, vývojári prichádza a odchádza, stráca sa povedomie o väzbách a procesoch a prehľad o tom, ako je databáza postavená. Končí to tak, že sa databáza začne používať iba ako odkladisko hromady dát, ktorá nejako funguje, hoci integrita je dávno tá tam. Samozrejme to nie je problémom všetkých spoločností, ale stretávam sa s tým celkom pravidelne. Až sa potom raz to koliesko poláme.

Napríklad takou maličkosťou, ako je odkaz na neexistujúci nadradený záznam, ktorý pritom ani nie je dôležitý a vlastne ho v danej chvíli nechceme. Požadovaný záznam bol dávno uvoľnený a nám nastáva skutočné peklo. Prejsť celú, mnohokrát niekoľko gigabajtovú databázu, dohľadať kolízie, opraviť záznamy, nastaviť reštrikcie, zaberie aj týždne tvrdej práce.

V nasledujúcom kvíze, Kvíz - Cudzie kľúče a reštrikcie v MySQL, si vyskúšame nadobudnuté skúsenosti z predchádzajúcich lekcií.


 

Predchádzajúci článok
Riešené úlohy k 21.-22. lekciu MySQL/MariaDB
Všetky články v sekcii
MySQL databázy krok za krokom
Preskočiť článok
(neodporúčame)
Kvíz - Cudzie kľúče a reštrikcie v MySQL
Článok pre vás napísal Petr Kateřiňák
Avatar
Užívateľské hodnotenie:
4 hlasov
Autor se věnuje tvorbě aplikací převážně v PHP frameworku Laravel na straně backendu a Angularu pro frontend.
Aktivity