Vazba tabulek podle typu

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

Dejme tomu, že máme v databázi objekty různého typu, např. hotely a auta. Nad všemi těmito objekty ale chceme provádět nějaké operace, např. vytvářet objednávky. Jakým způsobem navrhnout a provázat tabulky?

  1. Asi nejpřímočařejší a nejčastější přístup spočívá ve vytvoření tabulek hotely (id, …), auta (id, …) a objednavky (id, typ enum('hotel', 'auto'), id_objekt, …). Problém s tímto přístupem spočívá v tom, že nad sloupcem objednavky.id_objekt nelze definovat cizí klíč.
  2. Druhá možnost je vytvořit tabulku objednavky (id, id_hotel, id_auto, …). V tabulce můžeme správně definovat cizí klíče, kromě toho ale musíme zajistit, aby právě jeden z vazebních sloupců měl hodnotu jinou než NULL.
  3. Vytvoříme tabulku objekty (id) a do této tabulky se budeme odkazovat z ostatních tabulek – hotely.id_objekt bude zároveň primárním klíčem tabulky hotely a zároveň cizím klíčem do tabulky objekty. Vytvoření hotelu se tak mírně zkomplikuje (nejprve bude potřeba vložit záznam do tabulky objekty a jeho ID potom použít pro hotel). Kromě toho musíme zajistit, aby objekt existoval právě v jedné tabulce.
  4. Místo jedné tabulky objednavky vytvoříme tabulky hotely_objednavky a další. Problémem je náročnost změn v těchto tabulkách a nemožnost pracovat se všemi objednávkami v jedné tabulce.

Po pravdě řečeno se mi zdá jeden přístup horší než druhý a správné řešení tohoto problému bohužel neznám.

Jakub Vrána, Řešení problému, 31.7.2008, diskuse: 36 (nové: 0)

Diskuse

Marek:

Hlasuju pro 2), pripadne 3) (pokud tech moznych entit bude vic)

Hever:

Hlasuju pro 2)

Koubas:

"Koho enum napadne, tomu ruka upadne", kde sem to jen slysel ;)

p360t:

Nedávno som toto riešil a zvolil som si možnosť 3. Prvý a druhý spôsob som zavrhol, štvrtý mi ani nenapadol, ale keď naň tak pozerám, tiež by som si ho nevybral.

Martin:

1) a referencni integritu resit triggerem

finc:

Když bych si dane tabulky predstavil jako objekty v db, vyšel by mi asi následující model:
<?php
class Polozka {
  private $id;
  // getters, setters
}
class
Hotel extends Polozka {
}
class
Auto extends Polozka {
}
class
Objednavka {
//...
/**
  * @var Polozka
  */
private $polozka;
}
?>

Proto mě osobně nejvíce sedí číslo 3. Otázkou poté spíše zůstává, zda používám nějaký ORM framework, který dědičnost umí, či jen "ploché" získávání dat, které sebou přináší ty problémy při akčních dotazech.

PHX:

Naprosto souhlasim. Asi nejrozumnejsi reseni dle logiky. Dedicnost v databazi. Nejprve vlozis spolecne udaje do tabulky objekt a pote rozsirujici do prislusne tabulky. Nebo vyuzijes nejakou ulozenou proceduru.

Otazkou je jak hodne rozdilna je prace s ruznymi typy.

Pawell:

3) INSERT/UPDATE/DELETE nad tabulkou objekty osetrime pomoci INSERT/UPDATE/DELETE trigeru nad tabulkou hotely/auta, pricemz primarni klic (idecko) v tabulce hotely/auta bereme z se spolecne sekvence.

5) Pouzijeme Doctrine a jeji Column aggregation inheritance

ADRIVE:

Ten bod 5 nie je náhodou bod 3.? Inak doctrine využívam práve kvôli column aggregation...

Ondrej Ivanic:

IMHO, riesenie 3 je asi najrozumnejsie. Zo skusenosti som vsak zistil ze ked uz treba robit nieco taketo tak
a) snazime sa urobit nieco az velmi genericke
b) nepochopili sme zadanie

Da sa este pouzit VIEW ktory bude obsahovat union vsetkych potrebnych tabuliek (problem su stlpce - ktore vybrat a z ktorej tabulky). Potrebne je pouzit jednu globalnu sekenciu ktora bude pouzita pre PK vo vsetkych tabulkach a tym padom budeme mat unikatne cislo aj vo view - ak to nie je mozne tak do bude treba pridat do view dalsi stlpec ktory bude urcovat zdroj, napr:

id | source | ...
1    car
2    hotel
2    car

potom ale (id, source) bude nieco ako PK. Pri PgSQL v takejto tabulke mozeme vytvorit aj insertovatelny view ktory podla stlpca `source` rozhodne do ktorej tabulky ulozi zaznam (viacnasobne create rule ...)

Andrew:

Na daný příklad bych volil určitě řešení 3. Na druhou stranu ale souhlasím, že takový případ by neměl nastávat, neboť většinou objednávka auta vypadá zcela jinak než objednávka hotelu a tudíž bychom se neměli snažit vytvářet něco příliš generického - indikuje to, že něco není úplně správně v návrhu. V reálné aplikaci tedy volím 4.
V praxi totiž tu generickou tabulku využijeme maximálně na nějaké reporty a statistiky, kde se ideálně hodí nějaký pohled. Pro inserty nemá generická tabulka moc smysl.

Ondrej Ivanic:

Zadanie je strasne vytrhnute z kontextu a kedze nepoznam celu domenu tak je dost tazko povedat ze co by z toho realne vyslo :)

ikona Jakub Vrána OpenID:

Hotel má úplně jiné vlastnosti než auto. Objednávka obojího je ale v podstatě stejná - informace o zákazníkovi, od kdy a do kdy, datum vytvoření, poznámka, ... Generická tabulka tedy podle mě dává smysl i pro vkládání nových objednávek a jejich následné zpracování.

Michal:

Nemůže být jedna tabulka se sloupcem "druh" - kde se bude definovat zda-li jde o auto či hotel, a potom sloupce param1, param2.... kde se napíšou parametry příslušného zboží.

M.

ikona Murděj:

Něco jako 3) používám pro položky faktury. Mam tabulku billing_item kde je id, typ položky a nějaké společné vlastnosti (cena, dph, ...) v tabulkach billing_item_* mám potom detailní data ke každé položce podle konkrétních typů.

Pro ukládání mám metodu která se stará o uložení dat do konkrétní detailové tabulky takže stačí zavolat

$billing->add_item('auto', 2299000, array('barva' => 'red', 'model' => 'Porsche Carrera'));
$billing->add_item('rum', 89.60, array('voltu' => '40%', 'obsah' => '1Litr'));

Pro přepočítání celkové částky faktury nebo různé jednodužší reporty stačí select z jedne tabulky.

Honza:

Jsem pro 3. s tou modifikací, že tabulka objekt bude obsahovat pole typ (možno navázat na číselník typů) a hotely a auta budou aktualizovatelné pohledy.

Jan Tichý:

Ještěže má PostgreSQL dědičnost tabulek, takže je tam tenhle problém vyřešen systémově a elegantně ;). Na úrovni čistě relační databáze je pak IMHO nejlepší varianta 3 jakožto nahrazení zmiňované dědičnosti. S tím, že se pak takové řešení může zpříjemnit pomocí rozličných triggerů či view.

Jan Tichý:

Mimochodem, tohle je ukázkový příklad praktického využití vazby 1:1.

Jakub Kulhan:

Mně se asi nejvíce pozdává řešení 4) s menšími změnami. Všechny objednávky by byly v jedné tabulce, každý druh objektu by měl svou tabulku (hotely, auta atd.) a ke každému druhu objektu by byla vazebná tabulka na objekdnávky (hotely_objednávky, auta_objednávky atd.). Ale hodně záleži, oč vlastně jde. Možná, že by se našel i nějaký lepší způsob.

Jakub Kulhan:

Tak si říkám, že jsem napsal naprostou blbost. Poučení pro přístě – než něco napíšeš, mysli :o)

marek:

není lepší mít místo enum něco jako id?  nemusel bych pak při přidání dalšího druhu měnit tabulku, ale jenom přidat číslo do pořadí....?

v zásadě se mi ale nejvíc líbí (tj. používám) varianta 3...

ikona v6ak:

IMHO není - přidání druhu je změna struktury.

Mirek:

Nevím, jestli nejsem úplně mimo, ale proč to neudělat třeba tak, že:

tabObjednavka
klic
(
- objednavka.id
- tabTypyPolozek.id
- "polozka".id (hotel.id, auto.id, kapesnik.id)
)
-- mer.jednotek

tabTypyPolozek
- id
- nazev
- tabJmeno.id

tabJmeno
- id
- nazevTabulky

... udrzuju jednu tabulku tabObjednavka - ostatni jsou ciselniky

radim:

Ano, je to funkční, jednoduchy, užitečny. Má to zásadní nevýhodu, není to programátorsky sofistikovany.
Zadani nema řešení,  nelze zjistit, zdali maji hotely svoje auta, či jde o auta samostatneho subjektu.
Ale i tak bude číselník s poddotazem stačit.

Mirek:

Ale tady přeci nejde o zjišťování, jestli auta patří hotelu anebo ničemu. Jde o problém, jak udělat strukturu tabulek pro skládání objednávek ze zboží různého typu (hotely, auta, kapesníky, zájezdy, ...) ... nebo mi asi něco uniká, tedy :o/

Honza:

Mirku,
tvůj návrh je v podstatě návrh č.1. Je tam problém, že takto není možno definovat cizí klíč.

Mirek:

... proč ne? S malou úpravou

tabObjednavka
- id... dá se použít jako cizí klíč
klic ... zajišťuje unikátnost kombinace
(
- objednavka.id
- tabTypyPolozek.id
- "polozka".id (hotel.id, auto.id, kapesnik.id)
)
- mer.jednotek

takto se mi problém zdá naprosto v pohodě řešitelný - bez komplikací. Ale v návrhu databází zas tolik zkušeností nemám - což neznamená, že bych se rád něco dalšího nenaučil.
Přidání id vedle klic(...) představuje nějaký možný zdroj potíží?

Mirek:

.. BTW ... není mi jasný důvod pro potřebu cizího klíče odkazujícího na tabulku s výčtem položek objednavky tabObjednavka ... leda, že by šlo o něco jako částečnou dodávku, kdy výčet položek je neúplný a pod. ... ale to lze řešit založením podobných tabulek s podobnou strukturou

tabDodavka
- id... dá se použít jako cizí klíč
klic ... zajišťuje unikátnost kombinace
(
- dodavka.id
- tabTypyPolozek.id
- "polozka".id (hotel.id, auto.id, kapesnik.id)
)
- mer.jednotek

a jejich párováním v pomocné tabulce

tabRealizace
- id
klic
(
- tabObjednavka.id
- tabDodavka.id
)

Nemám to promyšlené až do konce, ale takto se dá předejít problémům s časovou posloupností, když dodávka předstihne objednávku - myslím oficiální evidovanou objednávku.

Oldis:

vetsinou se resi ze je tabulka objednava(id, ...), pak tabulka tzv. vazebni (id_objednavky, id_produktu), s cizimi klici na obe strany, kdyz smazu objednavky smazou se i vazby(aby zbytecne nezabordelili tabulku) kdyz smazu produkt smazou se vazby(stejny duvod) ovsem kvuli historii a archivaci se produkty nikdy nemazou, jen se jim nastavi priznak smazano, aby meli i pet let stare objednavky platnost a nebyli prazdne, aby slo dohledat co v te objednavce bylo. reseni tri je patrne nejlepsim resenim, nejjednodussi na zpravu kodu, nemusim pridavat navic kontrolni mechanizmi, vychazim z id_entity a ke kazde entite mam zaznam v tabulkach konkretnich, genericnost je dobra vec. pokud se stim pocita uz pri navrhu modelu, staci odvozovat tridy od zakladni entity. a jejich tvorba muze byt klidne dynamicka, pres rekneme konfiguracni pole atd.. meze predstavivosti se nekladou

Marek Cevelicek:

v pripade 4/ se da dost usetrit na faktoru premysleni:)
1/ a 2/ bych dal hodne blizko sebe bo ve vyuzitelnosti to ma sve mouchy. presto jsem pripad 1/ jiz nekolikrat pouzil a pokud na tom clovek nechce provadet nejake extra slozite sql dotazy, tak je to podle me stredni cesta.
3/ je podle me velmi pekne vyreseni problemu

ikona pa3k:

Záleží na tom, či daná datová štruktúra (aplikácia) bude nemenná alebo či sa očakávajú zmeny, prípadne definovanie nových objektov a typov ich vlastností zo strany užívateľa. Pre projekt kde sa to meniť nebude, je to jedno, bohate stačí variant 1 alebo 2.

Pre variabilné riešenie - kde je potreba definovať nové objekty a ich špecifické vlastnosti je IMHO vhodné pre všetky objekty definovať jednu spoločnú tabuľku. V nej hodnoty spoločné pre všetky objekty, napríklad dostupnoť, cena, DPH a pod. Atribúty objektov ktoré sú špecifické zas linkovať do samostatnej tabuľky atribútov, cez tabuľku ktorá definuje typy atribútov pre každý objekt.

štíhloprd:

Dle mého skromného názoru:
1. je nejpřehlednější (pro toho, kdo to má programovat) a nejodolnější vůči nějakým nekonzistentním stavům, které by mohly nastat. sloupec 'typ' (který by určitě měl být indexován) se mi ale přesto z nějakého důvodu nezdá úplně "košér". pamatoval bych též na to, že enum může uchovávat omezené množství hodnot (např. 32 nebo 64). vhodné pro řešení, kde objem dat nebude vyžadovat nějakou zásadní optimalizaci.

2. pro velký počet objektů rapidně narůstá objem prohledávaných / editovaných dat, z nichž navíc stále větší část tam bude jalová. některé databáze umožňují "jen" 255 sloupců, což je limit, který by ve většině případů měl stačit, ale člověk nikdy neví :-). pak by se mi nelíbilo mít tabulku s 200 sloupci, kde jenom jeden z nich nese nějakou informaci.

3. se jeví jako akademické, normalizované řešení, které 'emuluje' abstrakci, polymorfismus a dědičnost, kde jsou avšak jednotlivé objekty "roztahané" do dvou tabulek - jakýkoli výběr, editace, smazání probíhá buď přes dva dotazy nebo jejich spojení pomocí JOIN. pokud by objednávky tvořily jen zlomek celkové agendy a hlavní nápor pro databázi byl ve vyhledávání popř. editování objektů, pak by to znamenalo určitou zátěž na databázový stroj.

4. bych použil, pokud by spolu s objednávkou bylo potřeba zaznamenat ještě něco navíc, co se liší podle tabulky (např. u objednávky auta SPZ, u objednávky hotelu počet nocí). pokud by bylo objednávek např. milion pro auta a sto pro hotely, pak oproti ostatním řešením by byl výkonnostní rozdíl oproti vyhledávání v objednávkách pro hotely, protože zde by se použila jen tato menší tabulka. pokud by naopak objektů bylo několik stovek a pro každý 2-3 objednávky, pak by se zase nelíbilo, jak databázový systém harddiskem "hrabe" do každého souboru tabulky zvlášť (jak tomu je u většiny z nich). Ad 'Nemožnost pracovat se všemi objednávkami') pokud by daná aplikace měla nabízet vyhledávání objednávek v objektech, které si uživatel může zatrhnout, pak bych toto řešení viděl jako výhodu oproti projíždění jedné velké tabulky pokaždé u ostatních řešení.

já osobně se spíš vyhýbám řešením, kde je potřeba něco předpokládat, a kde jinak nastávají nejednoznačné stavy,

štíhloprd:

Mimochodem, Jakube, - článků jako je tenhle je na internetu jako šafránu a konkrétně diskuze pod nimi je pro ostatní knezaplacení™. Je moc dobře, že tě i po 4 letech napadají!

Milan:

Sám jsem před nedávnem použil mírnou alternativu 3). A mám dotaz: ke všem objektům se dají přidávat komentáře. Tabulka má strukturu: komentare ( id_komentare, id_objektu, komentar, ...). Chci vypsat seznam všech objektů, které obsahují nějaký komentář, ve formátu:

Kometáře automobilů
- Volvo
- Audi
Komentáře hotelů
- Hotel na pláži
- Hotel na kopci

Je jiná a lepší možnost, než připojit všechny tabulky (hotely, auta, ...) a pak zkoumat: IF( hotely.id_objekt IS NOT NULL...) nebo pro každý objekt extra dotaz? Díky

ikona Jakub Vrána OpenID:

Asi bych to udělal ve více jednoduchých dotazech:

FROM auta INNER JOIN komentare
FROM hotely INNER JOIN komentare

Martin:

Ahoj. Ten samý problém řeším po 11 letech. Existuje už nějaké používané řešení? Mne zatím napadlo jedině obrátit závislosti přidáním dvou spojovacích tabulek:

1. Tabulka auta,
2. Tabulka hotely,
3. Tabulka objednavky_souhrn,
4. Tabulka objednavky_hotelu
s cizími klíči hotely_id a objednavky_souhrn_id,
5. Tabulka objednávky_aut
s cizími klíči auta_id a objednavky_souhrn_id.

Je to trochu zamotané, vytváření i mazání bude dost složité (mazání lze snad vyřešit triggerem u tabulky objednávky_souhrn, vkládání transakcí) a mohou nastat podobné problémy jako u řešení 2, popř. 3, ale zatím mi to přijde použitelné. Co si o tom myslíte? Nebo už MySQL umí něco, co nebylo před 11 lety k dispozici?

Vložit komentář

Používejte diakritiku. Vstup se chápe jako čistý text, ale URL budou převedeny na odkazy a PHP kód uzavřený do <?php ?> bude zvýrazněn. Pokud máte dotaz, který nesouvisí s článkem, zkuste raději diskusi o PHP, zde se odpovědi pravděpodobně nedočkáte.

Jméno: URL:

avatar © 2005-2018 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.