Dědičnost v SQL databázi
Srp/09 2
Moderní relační databáze přejímají i některé myšlenky z objektového paradigmatu. Díky tomu můžeme využívat dědičnost, kterou známe z objektového programování i v databázi. Dnes se podíváme na to, jak dědičnost funguje v PostgreSQL.
Co je dědičnost?
Dědění je v OOP vztah mezi „předkem“ a „potomkem“. Anglicky se tento vztah označuje jako „IS-A“ tzn. „je“ – potomek je předkem a zároveň má nějaké dodatečné vlastnosti.
Mějme např. domácí sbírku, která obsahuje položky. Položka je předek a její potomci jsou knihy, filmy, nebo hudebniny. Každá položka má autora a rok vydání, ale knihy mají navíc počet stránek, kdežto vlastností hudebního alba jsou skladby, které obsahuje.
Potomci mají vlastnosti předka (rok vydání, autora) plus nějaké svoje specifické vlastnosti, které jiní potomci nebo předek nemají (počet stran, skladby).
Když ale chceme evidovat, co jsme komu půjčili, pracujeme obecně s položkou sbírky – abstrahujeme od toho, zda se jedná o knížku nebo film, prostě si poznamenáme, co a kdy jsme komu půjčili.
Objektové programování vs. databáze
V objektovém programování pracujeme s třídami a instancemi (objekty). Třídy jsou vzorem objektu (např. třída Položka, nebo třída Kniha). Zatímco instance je konkrétním výskytem objektu (např. konkrétní kniha). V OOP funguje dědičnost na úrovni tříd.
Relační databáze stojí na jiném paradigmatu než objektové programování, ale přesto tu můžeme nalézt volnou analogii. Na databázovou tabulku se můžeme dívat jako na třídu a na jednotlivé záznamy jako na instance této třídy.
V databázích, které podporují dědičnost si tedy můžeme vytvořit tabulky, které od sebe budou dědit určité atributy (sloupečky). Vytvoříme si tedy tabulku „položka“ se sloupečky „rok vydání“ a „autor“ a tabulku „kniha“, která bude jejím potomkem a bude navíc obsahovat atribut „počet stran“.
Zajímavou vlastností je, že se dědičnost netýká jen struktur (sloupečků), ale i vlastních dat. Díky tomu v tabulce „položka“ vidíme nejen záznamy, které jsme v této tabulce vytvořili, ale i všechny záznamy z tabulek, které jsou potomkem položky (knihy, filmy). V tabulce „položka“ samozřejmě vidíme jen ty sloupečky, které jsou společné všem, zatímco v tabulkách potomků vidíme i jejich specifické atributy.
Oproti např. Javě se databázová dědičnost v PostgreSQL liší ještě jednou věcí: tabulka může být potomkem více přímých předků, a dědí tak všechny jejich atributy.
Využití dědičnosti v PostgreSQL
Po přečtení oficiální dokumentace budeme možná trochu zklamaní. Narážíme totiž na problém s primárními a cizími klíči.
- Vytvoříme-li totiž v tabulce potomka záznam s primárním klíčem 1 a v tabulce jiného potomka záznam se stejným primárním klíčem, máme v tabulce předků záznamy s duplicitními „primárními klíči“.
- Budeme-li se chtít pomocí cizího klíče odkázat na tabulku předka (např. výpůjčka obecné položky), můžeme se odkázat pouze na záznamy, které jsou přímo v tabulce „položka“ nikoli na záznamy v tabulkách potomků.
Tím se ale nenecháme odradit a ukážeme si řešení, jak využívat výhod dědičnosti i referenční integrity zároveň.
Řešení spočívá ve vytvoření jedné pomocné tabulky a použití pravidel (rule). Pomocná tabulka obsahuje jediný sloupeček (číselné ID), nad kterým uděláme primární klíč. Tabulky předka i potomků na její primární klíč navážeme pomocí cizích klíčů.
Ukázka
Vytvoříme si databázové tabulky pro naši domácí sbírku, která obsahuje knížky, filmy a hudbu. Tyto tři tabulky budou potomky tabulky „položka“. Dědičnost nám umožní např. snadno přidávat další společné atributy (sloupečky) do všech tabulek (přidáme je jen do tabulky předka) a můžeme se pomocí cizích klíčů z jiných tabulek odkazovat jak na jednotlivé konkrétní typy (knížka, film…), tak i na položky sbírky obecně.
Tabulka „polozka“ – předek ostatních tabulek:
CREATE TABLE sbirka.polozka ( id bigint NOT NULL DEFAULT nextval('sbirka.polozka_seq'::regclass), nazev character varying(255) NOT NULL, datum timestamp with time zone DEFAULT now(), rok_vydani integer, autor bigint, CONSTRAINT polozka_pk PRIMARY KEY (id), CONSTRAINT polozka_autor_fk FOREIGN KEY (autor) REFERENCES sbirka.autor (id) MATCH SIMPLE ON UPDATE NO ACTION ON DELETE NO ACTION, CONSTRAINT polozka_fk FOREIGN KEY (id) REFERENCES sbirka.polozka_id (id) MATCH SIMPLE ON UPDATE NO ACTION ON DELETE NO ACTION DEFERRABLE INITIALLY DEFERRED ) WITH (OIDS=FALSE); ALTER TABLE sbirka.polozka OWNER TO sqlvyukavlastnik; GRANT ALL ON TABLE sbirka.polozka TO sqlvyukavlastnik; GRANT SELECT ON TABLE sbirka.polozka TO sqlvyukapiskoviste; -- Index: sbirka.fki_polozka_autor_fk -- DROP INDEX sbirka.fki_polozka_autor_fk; CREATE INDEX fki_polozka_autor_fk ON sbirka.polozka USING btree (autor); -- Rule: "polozka_delete ON sbirka.polozka" -- DROP RULE polozka_delete ON sbirka.polozka; CREATE OR REPLACE RULE polozka_delete AS ON DELETE TO sbirka.polozka DO DELETE FROM sbirka.polozka_id WHERE polozka_id.id = old.id; -- Rule: "polozka_insert ON sbirka.polozka" -- DROP RULE polozka_insert ON sbirka.polozka; CREATE OR REPLACE RULE polozka_insert AS ON INSERT TO sbirka.polozka DO INSERT INTO sbirka.polozka_id (id) VALUES (new.id); -- Rule: "polozka_update ON sbirka.polozka" -- DROP RULE polozka_update ON sbirka.polozka; CREATE OR REPLACE RULE polozka_update AS ON UPDATE TO sbirka.polozka DO UPDATE sbirka.polozka_id SET id = new.id WHERE polozka_id.id = old.id;
Pomocná tabulka „polozka_id“:
CREATE TABLE sbirka.polozka_id ( id bigint NOT NULL, CONSTRAINT polozka_id_pk PRIMARY KEY (id) ) WITH (OIDS=FALSE); ALTER TABLE sbirka.polozka_id OWNER TO sqlvyukavlastnik; COMMENT ON TABLE sbirka.polozka_id IS 'Pomocná tabulka – slouží k řešení dědičnosti, PK a FK.';
Sekvence pro automatické číslování položek:
CREATE SEQUENCE sbirka.polozka_seq INCREMENT 1 MINVALUE 1 MAXVALUE 9223372036854775807 START 16 CACHE 1; ALTER TABLE sbirka.polozka_seq OWNER TO sqlvyukavlastnik;
Tabulka „kniha“ – potomek položky (ostatní tabulky potomků jsou analogické):
CREATE TABLE sbirka.kniha ( -- Inherited: id bigint NOT NULL DEFAULT nextval('sbirka.polozka_seq'::regclass), -- Inherited: nazev character varying(255) NOT NULL, isbn sbirka.isbn, pocet_stran integer, -- Inherited: datum timestamp with time zone DEFAULT now(), -- Inherited: rok_vydani integer, -- Inherited: autor bigint, CONSTRAINT kniha_pk PRIMARY KEY (id), CONSTRAINT kniha_polozka_fk FOREIGN KEY (id) REFERENCES sbirka.polozka_id (id) MATCH SIMPLE ON UPDATE NO ACTION ON DELETE NO ACTION DEFERRABLE INITIALLY DEFERRED ) INHERITS (sbirka.polozka) WITH (OIDS=FALSE); ALTER TABLE sbirka.kniha OWNER TO sqlvyukavlastnik; GRANT ALL ON TABLE sbirka.kniha TO sqlvyukavlastnik; GRANT SELECT ON TABLE sbirka.kniha TO sqlvyukapiskoviste; -- Rule: "kniha_delete ON sbirka.kniha" -- DROP RULE kniha_delete ON sbirka.kniha; CREATE OR REPLACE RULE kniha_delete AS ON DELETE TO sbirka.kniha DO DELETE FROM sbirka.polozka_id WHERE polozka_id.id = old.id; -- Rule: "kniha_insert ON sbirka.kniha" -- DROP RULE kniha_insert ON sbirka.kniha; CREATE OR REPLACE RULE kniha_insert AS ON INSERT TO sbirka.kniha DO INSERT INTO sbirka.polozka_id (id) VALUES (new.id); -- Rule: "kniha_update ON sbirka.kniha" -- DROP RULE kniha_update ON sbirka.kniha; CREATE OR REPLACE RULE kniha_update AS ON UPDATE TO sbirka.kniha DO UPDATE sbirka.polozka_id SET id = new.id WHERE polozka_id.id = old.id;
Všimněte si pravidel (rule), která slouží k zajištění integrity a jedinečnosti primárních klíčů potomků.
- Při INSERTu vkládáme zároveň záznam do pomocné tabulky „polozka_id“ – pokud bychom se pokoušeli vytvořit potomka s primárním klíčem stejným, jako má jiný potomek, neprošlo by to kvůli primárnímu klíči tabulky „polozka_id“
- Při UPDATu změníme i záznam v tabulce „polozka_id“ – díky tomu nemůžeme přiřadit potomkovi stejné ID jako má některá jiná položka.
- Při DELETu odstraníme i záznam z pomocné tabulky, abychom neblokovali příslušné ID pro případné další využití.
Cizí klíče u potomků a předka bylo potřeba definovat jako DEFERRABLE INITIALLY DEFERRED
. Bez toho by totiž selhávaly příkazy, které jsme definovali v pravidlech.
Snadné zjištění „třídy“ objektu
Z tabulky „polozka“ můžeme získat seznam všech položek v našem systému (knihy, filmy, hudba), ale nevíme, z které tabulky který záznam pochází.
Vyrobíme si proto pohled (view), díky kterému uvidíme ID, název a „třídu“ všech objektů:
CREATE OR REPLACE VIEW sbirka.polozka_trida AS SELECT p.id, p.nazev, pg_class.relname AS trida FROM sbirka.polozka p JOIN pg_class ON p.tableoid = pg_class.oid; ALTER TABLE sbirka.polozka_trida OWNER TO sqlvyukavlastnik; GRANT ALL ON TABLE sbirka.polozka_trida TO sqlvyukavlastnik; GRANT SELECT ON TABLE sbirka.polozka_trida TO sqlvyukapiskoviste; COMMENT ON VIEW sbirka.polozka_trida IS 'Pomocný pohled, který nám umožní snadno dohledat třídu dané položky podle jejího ID.';
V příštím článku si ukážeme, jak používat dědičnost i v databázových systémech, které ji samy od sebe nepodporují…
pouzivat opatrne
Pavel Stěhule6:06 odpoledne on Duben 1st, 2010
Podpora dedicnosti v PostgreSQL je spis pozustatkem experimentalni akademicke ery nez cilene udrzenou a rozvijenou funkci. Muzete narazit na nektere problemy - napr. s implementaci referencni integrity. Osobne bych se dedicnosti vyhl, vyjma partitioningu, kde neni jine reseni.
díky za článek
Kokeš10:10 odpoledne on Srpen 11th, 2009
Už se těším na další článek – tohle sice vypadá hezky, ale ne každý má to štěstí, používat databázi s funkcí dědičnosti.