Umíme to s Delphi: 79. díl – databázová aplikace Master/Detail

V předchozí části seriálu jsme vytvořili databázovou aplikaci s využitím BDE. Díl, který právě čtete, přináší přehled tzv. Master/Detail formulářů, které slouží k zobrazení souvisejících dat ze dvou databázových tabulek. Poznáme také několik úskalí, kterým bychom se měli vyhnout, a to nejen při tvorbě databázových aplikací, ale obecně v Delphi vůbec.

Formuláře 1:N

První věc, kterou ve dnešním díle zmíníme, je vytváření formulářů vzniklých na základě vztahu 1:N (o vztazích mezi databázovými tabulkami pojednává několik předchozích dílů). Formulářům využitelným pro zobrazení vztahu 1:N říkáme souhrnně „formuláře typu Master/Detail“ a jejich principem je, že pro zvolené pole primární tabulky (tzv. Master Field) se vypíšou všechna pole sekundární tabulky, která souvisí se zvoleným polem primární tabulky.

Co to znamená? Pro jeden záznam v primární tabulce (tzv. master tabulka) existuje v jiné, sekundární tabulce (detail tabulka) několik (typicky mnoho) souvisejících záznamů. Jako příklad můžeme uvést např. tabulky UZIVATELE a ADRESY|. Jeden uživatel může mít několik emailových adres. Dalším příkladem jsou zákazníci a objednávky apod.

Ukážeme si, jak v praxi koncept master/detail formulářů využít. Vyjdeme z příkladu, který jsme naprogramovali před týdnem (kuchaři a jídla), přičemž s naprosto minimálními modifikacemi by bylo možné využít níže vysvětlená fakta i v jiných případech, dokonce při práci s jinou databázovou architekturou.

Nejjednodušším (avšak zároveň nejprimitivnějším a nejnekontrolovanějším) způsobem je možné formulář master/detail vytvořit pomocí průvodce integrovaného v Delphi.

1. Vytvořte novou aplikaci. Poté v hlavní nabídce zvolte File – New – Other a na záložce Bussiness vyberte Database Form Wizard:

2. Potvrďte. Otevře se další dialogové okno, v němž zvolte Create a Master/Detail Form a dále Create a form using TQuery objects:

3. Klepněte na Next, otevře se další dialog. V něm vybíráme tabulku vystupující jako master. V rozbalovacím seznamu v pravé spodní části dialogu vyberte Kuchyn. V tom okamžiku se v dalších seznamech automaticky zjeví tabulky vyskytující se v naší databázi. Dejme tomu, že nás více zajímají kuchaři a jejich umění, proto jako master tabulku zvolíme právě kuchaře:

4. Klepnutím na Next se dostaneme o další krok dále. V dalším dialogu je nutné zvolit atributy (sloupce), které nás z tabulky zajímají (které chceme zobrazit na formuláři). Obecně platí, že pokud v databázi používáme k jako primární klíče jednoznačné identifikátory nemající žádnou souvislost s hodnotou atributu jako takovou (typicky při generování unikátního ID pro každý nový záznam), neměl by uživatel vůbec přijít s tímto ID do kontaktu – nezajímá jej, nemá pro něj žádný význam, a mate jej. Protože však budeme později muset specifikovat atributy, na jejichž základě se provede tzv. spojení tabulek, nezbývá nám nyní než označit všechny tři sloupce: JMENO, PLAT i IDKUCHARE, a pak klepnout na tlačítko označené „>“. Jiná možnost je neoznačovat nic a klepnout na „>>“. Výsledek by v každém případě měl vypadat takto:

5. Klepneme na Next. Následující dialog se nás táže, chceme-li zobrazit pole na formuláři horizontálně, vertikálně nebo v mřížce. Můžete si zvolit řešení, které je vám nejbližší; my zvolíme horizontálně a zaškrtneme tedy Horizontally.

6. Další dialog se nás táže, jakou tabulku hodláme využít jako typ detail. V jednotlivých seznamech jsou již předpřipravené údaje z minula (databáze Kuchyn včetně tabulek), takže vybereme tabulku UMIVARIT a potvrdíme Next.

7. Další dialog opět dobře známe: chce se po nás vybrat množina polí, které uvidíme na formuláři. Bohužel nemáme jinou možnost, než zvolit opět obě pole s ID, protože tabulka UMIVARIT neobsahuje žádné vhodnější atributy. Vybereme tedy IDJIDLA i IDKUCHARE, klepneme na šipku a na Next.

8. Další dialog vyžaduje volbu způsobu zobrazení položek z tabulky „detail“. Tentokráte zvolíme mřížku, tedy možnost In a grid.

9. Nyní se dostáváme ke klíčovému okamžiku celé operace. Musíme totiž říci, na základě jakých polí bude provedeno tzv. spojení tabulek. Jinak řečeno – která pole rozhodnou o tom, jaké záznamy z tabulky UMIVARIT patří k jednomu konkrétnímu záznamu tabulky KUCHARI. Z obou seznamů (Detail Fields i Master Fields) vybereme IDKUCHARE a klepneme na Add. V seznamu Joined Fields se objeví IDKUCHARE -> IDKUCHARE. To je v pořádku, klepneme tedy na Next:

10. Tím je formulář Master/Detail hotov. V posledním dialogu pouze zvolíme způsob generování aplikace: pokud bychom chtěli generovat i nový hlavní formulář (což není náš případ, neboť jsme předtím vytvořili novou aplikaci s prázdným formulářem), zaškrtli bychom „Generate Main Form“. Další volba spočívá v tom, má-li se celá aplikace včetně nevizuálních komponent vytvořit pouze na formuláři nebo jestli se má použít také datový modul. Vzhledem k tomu, že jsme si v předchozích dílech vychválili výhody datového modulu, s povděkem využijeme tuto možnost a zvolíme „Form and Data Module“. Klepneme na Finish a okamžik počkáme, než průvodce vygeneruje celou aplikaci.

11. Poté, co průvodce vygeneruje aplikaci, musíme provést poslední „maličkost“. Apliakce nyní totiž vypadá tak, že hlavním formulářem je Form1 (který již v aplikaci byl před spuštěním průvodce), a průvodce nyní přidat DataModule2 a formulář Form3. Formulář Form3 obsahuje všechny vizuální komponenty a v podstatě celou aplikaci, zatímco formulář Form1 je zbytečný. Ukážeme si, jakým způsobem upravit zdrojový kód projektu, abychom se nežádoucího formuláře Form1 zbavili a za hlavní formulář prosadili Form3. Úprava je nesmírně jednoduchá. V hlavní nabídce zvolte Project – View Source. Zdrojový kód projektu nyní vypadá takto:

program Project1;

uses
  Forms,
  Unit1 in `Unit1.pas` {Form1},
  Unit2 in `Unit2.pas` {DataModule2: TDataModule},
  Unit3 in `Unit3.pas` {Form3};

{$R *.res}

begin
  Application.Initialize;
  Application.CreateForm(TForm3, Form3);
  Application.CreateForm(TForm1, Form1);
  Application.CreateForm(TDataModule2, DataModule2);
  Application.Run;
end.

Zdálo by se, že úprava může spočívat v jednoduchém odstranění obou řádků, v nichž je zmínka o Form1. Zkusme to. Zdrojový kód nyní bude vypadat takto:

program Project1;

uses
  Forms,
  Unit2 in `Unit2.pas` {DataModule2: TDataModule},
  Unit3 in `Unit3.pas` {Form3};

{$R *.res}

begin
  Application.Initialize;
  Application.CreateForm(TForm3, Form3);
  Application.CreateForm(TDataModule2, DataModule2);
  Application.Run;
end.

Formulář Form1 zavřeme a je po něm. Nyní můžete celý projekt uložit (File – Save All).

Pozor!!!

Nyní raději rychle zapomeňte na to, co jsme společně právě vytvořili. Rád bych zdůraznil, že tento způsob úpravy projektu je odstrašujícím příkladem, který bychom neměli nikdy používat. Mnohem lepší, čistší a jistější je použít nástrojů IDE: v nabídce Project nalezneme položku Remove From Project, otevře se dialog a v něm nesmírně snadno vybereme Form1, resp. Unit1 a klepnutím na tlačítko tuto jednotku a formulář odstraníme z projektu.

Pokud bychom pouze změnili zdrojový kód projektu, jak jsme to předvedli v předchozích odstavcích, výsledkem by bylo, že formulář by se sice při spuštění aplikace nevytvořil, nicméně byl by stále součástí projektu, například by se ukládal (včetně „své“ jednotky Unit1.pas) apod.

Rád bych ještě jednou zopakoval, že považujeme-li za nutné provést jakékoliv úpravy ve zdrojovém souboru projektu, je téměř vždy lepší a jistější využití některého vizuálního nástroje integrovaného prostředí, než přímá úprava souboru DPR.

Zpět k formuláři Master/Detail

Vraťme se však k naší databázové aplikaci a k formuláři Master/Detail. Pokud nyní aplikaci spustíme (bez jakýchkoliv dodatečných úprav, doporučuji snad jen prodloužení editačního pole pro výpis platu, jinak se údaje do políčka nevejdou), bude bez problémů fungovat. Pomocí komponenty DBNavigator se můžeme posouvat po jednotlivých záznamech tabulky KUCHARI (po jednotlivých kuchařích) a v mřížce vždy uvidíme ID jídel, které daný kuchař dokáže připravit:

V následujícím odstavci vytvořenou aplikaci mírně analyzujeme.

Jak to všechno funguje?

Než se pustíme do zkoumání, jak naše aplikace funguje a jakou černou magii musel průvodce použít, aby toto geniální dílo vyprodukoval, rád bych poukázal na jeden problém. Asi se shodneme na tom, že aplikace je sice překrásná, ale výpis identifikačních čísel jídel nikoho příliš neohromí. Chtělo by to, aby namísto ID jídla aplikace dokázala rovnou zobrazit jeho název. To však při použití průvodce (jak jsme si to předvedli výše) není možné. Proč?

Je třeba si uvědomit, že abychom dokázali zobrazit název jídla, museli bychom použít tabulku JIDLA, která tyto názvy obsahuje. Co nám tedy bránilo zvolit v průvodci namísto tabulky UMIVARIT tabulku JIDLA? Situace by přece byla růžová: měli bychom formulář Master/Detail pro tabulky KUCHARI a JIDLA, přičemž KUCHARI by byl Master a JIDLA Detail. Pro každého kuchaře by se pak rovnou zobrazovaly názvy jídel, které umí připravit.

Situace by sice byla růžová, ale bohužel není možná, protože formulář Master/Detail je určen k prezentování dat z tabulek, které jsou navzájem ve vztahu 1:N. Tabulky KUCHARI a JIDLA v takovém vztahu nejsou, jsou ve vztahu M:N, neboť jeden kuchař umí vařit více jídel a jedno jídlo může býti připraveno více kuchaři. Z toho důvodu vůbec možné rozumným způsobem svázat tabulky JIDLA a KUCHARI. V návrhu databáze jsme ostatně tento problém již řešili a vyřešili zavedením tabulky UMIVARIT, která je tzv. vazební tabulkou, díky níž se vazba M:N rozpadá na dvě vazby 1:N (jeden kuchař má více záznamů v tabulce UMIVARIT, ale jeden záznam z tabulky UMIVARIT se týká výhradně jednoho kuchaře. Stejně tak jedno jídlo má v tabulce UMIVARIT více záznamů, ale jeden záznam tabulky UMIVARIT se týká výhradně jednoho jídla). Proto jsme museli při vytváření formuláře použít ke kuchařům jedině tabulku UMIVARIT, proto nyní nedokážeme zobrazit nic víc než ID jídel, která umí připravit jednotliví kuchaři, a proto jsme si tedy právě prakticky ověřili, že formulář Master/Detail je možné použít výhradně pro modelování vztahu 1:N.

To by bylo nepříjemné, protože jinak funguje průvodce poměrně hezky a zachrání nás před nutností klikat několik komponent na formulář a vyplňovat jejich vlastnosti. Za chvíli uvidíme, že naštěstí nebude velkým problémem upravit stávající aplikaci tak, aby dokázala zobrazovat názvy jídel, ne pouze jejich identifikační čísla.

Pojďme zjistit, jak vlastně naše aplikace funguje. Průzkumem datového modulu zjistíme, že obsahuje dvě komponenty DataSource a dva dotazy (komponenty) Query. Komponenta Query1 obsahuje ve své vlastnosti Query následující dotaz:

Select
  kuchari."Idkuchare",
  kuchari."Jmeno",
  kuchari."Plat"
From "kuchari.db"
As kuchari

Komponenta Query2 pak v téže vlastnosti obsahuje tento dotaz:

Select
  umivarit."Idkuchare",
  umivarit."Idjidla"
From "umivarit.db"
As umivarit
Where
  "umivarit"."Idkuchare" =:"Idkuchare"

Někteří z vás možná na základě těchto dvou dotazů vydedukují způsob, jakým master/detail aplikace fungují. Ostatní mohou prozatím přemýšlet, protože podrobný výsledek bádání, včetně mnoha dalších informací, si shrneme za týden.

Na závěr

Dnes jsme okusili formuláře Master/Detail. Naučili jsme se vytvářet je pomocí průvodce Database Form Wizard. V příštím díle se touto problematikou budeme zabývat mnohem více do hloubky; aplikaci důkladně popíšeme, analyzujeme, pochopíme a nakonec vylepšíme.

Diskuze (3) Další článek: Plus! Digital Media Edition: multimediální vitamíny

Témata článku: , , , , , , , , , , , , , , , , , , , , , , , , ,