Symetrické vazby

Školení, která pořádám

Pokud chceme sledovat vazby mezi položkami stejné tabulky (např. podobné výrobky), nejspíš za tím účelem vytvoříme dvousloupcovou tabulku vyrobky_podobne (vyrobek, podobny), kde oba sloupce budou směřovat na primární klíč hlavní tabulky. Vazba nemusí být symetrická, v tom případě se s tabulkou pracuje jako s běžnou vazební tabulkou a faktu, že oba sloupce ukazují do stejné tabulky, si nemusíme všímat.

Pokud má však být vazba symetrická (takže když je jeden výrobek podobný druhému, tak je také druhý podobný prvnímu), je zbytečné ukládat do tabulky obě vazby. Tento přístup ale zkomplikuje všechny dotazy do tabulky – místo vyrobek = @id musíme psát @id IN (vyrobek, podobny), při vypisování nebo spojování s dalšími tabulkami se to zkomplikuje o nutnost zjišťovat, jestli je vazba ve sloupci vyrobek nebo podobny.

Jak z toho ven? Řešení je překvapivě jednoduché. Stačí nadefinovat pohled:

CREATE VIEW vyrobky_podobne_vsechny AS
SELECT vyrobek, podobny FROM vyrobky_podobne
UNION
SELECT podobny, vyrobek FROM vyrobky_podobne

Data potom můžeme vybírat z tohoto pohledu, mazat je ze základní tabulky pomocí zmíněného @id IN (vyrobek, podobny) a vkládat obvyklým způsobem.

Někdo data organizuje tak, aby v jednom sloupci byla vždy menší hodnota než ve druhém, při vytvoření pohledu to je ale zbytečná práce navíc díky čemuž se nemůže stát, že by v tabulce byla stejná vazba dvakrát. Pohled nám tyto řádky sice sjednotí do jednoho, ovšem pouze za předpokladu, že v tabulce není další sloupec s různou hodnotou v jednotlivých řádcích. Pro zajištění konzistence tedy můžeme použít trigger:

CREATE TRIGGER vyrobky_podobne_bi BEFORE INSERT ON vyrobky_podobne FOR EACH ROW SET @vyrobek = NEW.vyrobek, NEW.vyrobek = LEAST(@vyrobek, NEW.podobny), NEW.podobny = GREATEST(@vyrobek, NEW.podobny);
CREATE TRIGGER vyrobky_podobne_bu BEFORE UPDATE ON vyrobky_podobne FOR EACH ROW SET @vyrobek = NEW.vyrobek, NEW.vyrobek = LEAST(@vyrobek, NEW.podobny), NEW.podobny = GREATEST(@vyrobek, NEW.podobny);

Přijďte si o tomto tématu popovídat na školení Návrh a používání MySQL databáze.

Jakub Vrána, Dobře míněné rady, 23.5.2007, diskuse: 6 (nové: 0)

Diskuse

SiseL:

dobre mienena rada!

rmaslo:

Stejný princip se hodí když evidujeme třeba nějaký zápasy a pak z nich dělame nějaký statistiky a zápas chceme zadavat jenom jednou...
Prostě přes union si definujeme view pomocí kterýho uděláme snadno ze záznamu
Česko|Rusko|5|0
záznamy
Česko|Rusko|5|0
Rusko|Česko|0|5
Pak se třeba počet vstřelených a inkasovaných gólů přes tým počítá poměrně snadno

Ondrej Ivanic:

Zaujimave a pekne riesenie
Ale aj tak by som asi vkladal radsej dva riadky. Zaujimala by ma vykonnost takehoto riesenia, ci to vobec stoji za to. S MySQL 5 som skoro vobec nerobil a tak neviem posudit...

ikona Jakub Vrána OpenID:

S vkládáním dvou řádek je problém ten, že může vzniknout nekonzistence.

Výkonnosti bych se nebál, UNION se optimalizuje výtečně.

RATMex B:

Ako môže vzniknúť u dvojitého vkladania nekonzistencia keď niekto nevloží prepísaným spôsobom, tak môže vzniknúť nekonzistencia aj u UNION-u, pokiaľ niekto nebude mazať predpísaným spôsobom.

Spoločným menovateľom oboch kritických bodov je zmena záznamov a nie výber, preto by bolo lepšie tento problém riešiť na úrovni triggerov. Do tabuľky vkladať a mazať ako z "jednozáznamovej", triggery by z nej robili fyzicky dvojzáznamovú. Výber by prebiehal normálne.

ikona Jakub Vrána OpenID:

Nikoliv, mazáním nekonzistence vzniknout nemůže - buď tam jsou oba záznamy nebo žádný.

Zajistit fyzické vložení záznamu pomocí triggeru je také dobré řešení.

Diskuse je zrušena z důvodu spamu.

avatar © 2005-2024 Jakub Vrána. Publikované texty můžete přetiskovat pouze se svolením autora. Ukázky kódu smíte používat s uvedením autora a URL tohoto webu bez dalších omezení Creative Commons. Můžeme si tykat. Skripty předpokládají nastavení: magic_quotes_gpc=Off, magic_quotes_runtime=Off, error_reporting=E_ALL & ~E_NOTICE a očekávají předchozí zavolání mysql_set_charset. Skripty by měly být funkční v PHP >= 4.3 a PHP >= 5.0.