Object Query Language Daniel Fromek Miroslav Novotný
MFF UK 2 0 0 5
Relace vs. Objekty ‣
‣ ‣
‣
Z psychologického hlediska nám jsou bližší objekty. Žijeme v „objektovém“ světe – milionům let evoluce vděčíme za to, že umíme objekty rozeznávat, přiřazovat jim jejich vlastnosti a chování. Tohoto faktu již dávno využívají různé modelovací prostředky jako např. UML. Když proto budeme používat objekty na úrovni implementace databáze, snadněji se nám bude provádět mapování nějakého schématu databáze navrženého pomocí např. UML na konkrétní implementaci. Při použití objektové databáze zabráníme jevu, který se v anglické literatuře označuje jako „Impedance mismatch“. Nastává, když chceme spojit dva systémy navzájem velmi konceptuálně rozdílné (když přistupujeme k relační databázi pomocí objektového programu)
Objektově-orientované koncepty ‣
‣
‣
Objektově-orientované databázové modely přejímají koncept objektověorientovaných programovacích jazyků. Neexistuje však žádná dohoda o přesné definici toho, jak mají objektověorientované systémy vypadat Následující definice jsou nejtypičtější
Koncepty - pokračování ‣
Komplexní objekty ‣
‣
Vztahy, asociace, linky ‣
‣
Databáze by měla sestávat z jednotlivých objektů. Tyto objekty v sobě mohou obsahovat další objekty. Každý objekt v sobě obsahuje unikátní interní identifikátor (OID), jež ho jednoznačně identifikuje. Dále obsahuje jedno nebo více externích jmen, které používají programátoři pro přístup k objektu. Objekty jsou mezi sebou propojeny konceptuálními linky (např. Zaměstnanec a oddělení jsou spojeny linkem pracuje_v). Na úrovni datových struktur se konceptuální linky realizují pomocí ukazatelů
Zapouzdření, skrývání dat ‣
Data objektu jsou rozdělena na privátní a veřejná. Z vnějšku jsou vidět jen data veřejná (viz. C++)
Koncepty – pokračování 2 ‣ ‣
Abstraktní datové typy (ADT) Třídy, typy, rozhraní ‣
‣
Operace, metody a zprávy ‣
‣
Vztah mezi třídou a objektem je stejný jako v C++. K objektu se přistupuje pomocí jeho rozhraní. Rozhraní poskytuje úplnou informaci nutnou k používání daného objektu S objektem je svázána sada operací (stejně jako v OOP se jim říká metody). Objekt danou operace vykoná po přijetí zprávy s jejím jménem a parametry
Dědičnost ‣
Funguje stejně jako v OOP
Koncepty – pokračování 3 ‣
Polymorfismus, přetěžování ‣
‣
Operace, která se má provést se vybírá dynamicky – poté, co objekt obdrží zprávu se jménem operace. Stejná zpráva pro různé objekty může vyvolat různé metody (viz. C++)
Persistence ‣
Databázové objekty jsou persistentní – mohou přežít programy, ve kterých byly vytvořeny.
Koncepty – dokončení ‣
‣
Různé představy vývojářů o technických detailech, které nejsou pokryty v předchozích konceptech, ale jsou nutné při konkrétní realizaci mohou vyústit v to, že se dané koncepty (třída, typ, ADT…) v jednotlivých produktech velmi liší (jak po technické tak i po praktické stránce) Nedostatek obecně akceptovaných definic týkajících se objektového modelu je považována za velký nedostatek objektově-orientovaných databází.
ODMG Standard ‣ ‣
Object Data Management Group Standard sestává z následujících částí ‣
‣
‣
Object Model – definuje význam základních elementů (třídy, objekty, rozhraní, vztahy… – viz. předchozí slajdy). Snaží se být nezávislý na konkrétním programovacím jazyku Object Definition Language (ODL) – je v podstatě rozšířením CORBA IDL. Definuje schéma databáze – objekty, vztahy mezi nimi, atributy objektů, operace, které se s danými objekty dají provádět, atd. ODL je nezávislé na konkrétním programovacím jazyku. Object Interchange Format – definuje reprezentaci objektů pro vzájemnou výměnu mezi různými OOSŘBD
ODMG Standard ‣ ‣
Object Query Language (OQL) – analogie SQL pro objektově orientované databáze Vazby na programovací jazyky – C++, Smalltalk, Java. Definuje, jak „naroubovat“ ODL a OQL na konstrukce výše zmíněných programovacích jazyků
Příklady OOSŘBD + praxe ‣
‣
‣
Mezi čistě objektové SŘBD můžeme zařadit: Jasmine (Fujitsu), GemStone, Odapter (HP), O2 (ODMG), ObjectStore, Poet… Na druhé straně autoři relačních databází do svých produktů přidávají objektové prvky (ADT, …) a vznikají tak produkty označované jako Objektově-relační databáze Obecně se očekává, že oba proudy – čistě objektový i objektově-relační v budoucnosti splynou (resp. Objektově-relační proud přejde v objektový). Kdy se tak stane, se ale přesně neví.
Object Query Language
OQL – principy ‣
OQL je nadmnožina té části SQL, která umožňuje pokládat dotazy do databáze. Tedy stejná konstrukce select která v SQL pracuje nad tabulkami může v OQL pracovat nad kolekcemi objektů.
‣
OQL nezahrnuje žádné modifikační operátory. O modifikaci objektů by se měly starat metody definované v přímo objektech.
‣
OQL může být voláno přímo z programovacích jazyků v kterých je definován objektový model.
OQL – schéma class Klient public type jmeno: String, objednavka: list(tuple( nazev: String, cena: Real)) end;
class Zamestnanec public type jmeno: String, datum_narozeni: Date, pozice: String, plat: Real, method age: integer end;
OQL – schéma class Mnozina_zamestnancu public type unique set (Zamestnanec) end; class Seznam_klientu public type list (Klient) end; class Spolecnost public type jmeno: String, zamestnanci: Mnozina_zamestnancu, klient: Seznam_klientu end;
OQL – schéma ‣
Každý dotaz do databáze potřebuje vstupní bod. ‣
Nad databází může být definováno více vstupních bodů. Jsou to pojmenované objekty nebo hodnoty.
name Globe: Spolecnost; constant name zamestnanci: Mnozina_zamestnancu;
OQL – základní dotazy. ‣
Vracejí atomické hodnoty, struktury nebo objekty.
‣
Cesta k elementu začínajicí nějakým vstupním bodem.
‣
Příklad: ‣
Jméno prvního klienta společnosti Globe. Globe.klient[0].jmeno;
OQL – select ‣
OQL přebírá z SQL konstrukci 'select'.
‣
Zde slouží k získání elementů splňující podmínky z kolekce dat.
‣
Výsledek je opět kolekce.
‣
Příklady kolekcí: ‣
set – neuspořádaná množina bez duplikatů.
‣
bag – neuspořádaná množina s duplikáty.
‣
list – uspořádaný seznam s duplikáty.
OQL – select ‣
Příklad dotazu na zaměstnance společnosti Globe s platem větším než 20 000. ‣ Výsledek dotazu je kolekce zaměstnanců typu 'bag'. ‣ Při použití klíčového slova distinct získáme kolekci typu 'set'.
select e from e in Globe.zamestnanci where e.plat > 20000;
OQL – select ‣
Spojovaní kolekcí – join. ‣
‣
Málo používané.
Příklad: Zaměstnanci společnosti Globe, kteří mají stejné jméno jako některý z klientů.
select e from e in Globe.zamestnanci, c in Globe.klienti where e.jmeno = c.jmeno;
OQL – select ‣
Můžeme položit dotaz, který pracuje s více než s jednou kolekcí a spojovat je specifikováním cesty.
‣
Příklad: Názvy objednávek klienta 'Novák'. select distinct obj.nazev from k in Globe.klienti, obj in k.objednavka where k.name = 'Novák'; výsledek typu: set(String);
OQL – konstrukce výsledků. ‣ ‣
Pomocí konstruktoru struct, bag, set a list můžeme ovlivnit strukturu výsledku dotazu. Příklad:
select struct (zamestnanec: struct( jmeno: e.name, vek: e.vek), plat: e.plat) from e in Globe.zamestnanci;
zamestnanec plat
jmeno vek
Novák 41 21000
OQL – agregační operátory ‣
Agregační operátory count, sum, max, min, avg.
‣
Aplikované na kolekce.
(výraz)
‣
max ( select e.plat from e in Globe.zamestnanci where e.pozice = 'reportér' ); select count(k.objednavka) from k in Globe.klienti;
OQL – operátor define ‣
Slouží k pojmenování výsledku dotazu. ‣ ‣
Jedná se pouze o pojmenování výsledku a ne dotazu samotného. Pojmenovaný výsledek můžeme použít v dalších dotazech.
define moji_zamestnanci as select e from e in Globe.zamestnaci where e.jmeno like 'N*'; select e.plat from e in moji_zamestnanci;
OQL – operátor element ‣
Pokud máme kolekci obsahující pouze jeden element můžeme daný element získat použitím operátoru element.
element (
select e from e in Globe.zamestnanci where e.jmeno = 'Svoboda');
OQL – existenční kvantifikátor ‣
Příklad: Jména všech společností s nějakým zaměstnancem mladším 23 let.
name VsechnySpolecnosti: list(spolecnost); select c.jmeno from c in VsechnySpolecnosti where exists e in c.zamestnanci: e.vek < 23;
OQL – operátor exists ‣
‣ ‣
operátor exists(exp), kde exp je kolekce vrací true pokud kolekce exp obsahuje alespoň jeden prvek. operátor unique(exp) vrací true pokud kolekce obsahuje právě jeden prvek. Navrženy tak aby umožňovali vnořené dotazy tak jak jsme na ně zvyklý z SQL.
select c.jmeno from c in VsechnySpolecnosti where exists ( select e from e in c.zamestnanci where e.vek < 23)
OQL – univerzální kvantifikátor ‣ ‣
Podobně jako existenční kvantifikátor se použije i univerzální kvantifikátor. Příklad: Test jestli všichni zaměstnanci společnosti mají alespoň minimální plat.
for all e in Globe.zamestnanci: e.plat > 10000; Vrací true pokud všichni zaměstnanci společnosti Globe mají plat větší než 10 000.
OQL – operátor group by ‣ ‣
Seskupuje objekty kolekce se stejnou hodnotou vybraných atributů. Ve výsledku jsou atributy podle kterých se seskupovalo a kolekce objektů nazvána partition. select * from e in Globe.zamestnaci group by e.plat; Výsledek je typu: bag ( struct ( plat: Real, partition: bag(zamestnanec))));
OQL – operátor group by ‣
Objekt partition může využít pro další výpočty pro každou skupinu. select e.plat, pocet: count(partition) from e in Globe.zamestnanci group by e.plat; Výsledek je typu: bag ( struct ( plat: Real, pocet: Integer));
OQL – operátor group by ‣ ‣
Lze sjednocovat podle více atributu. Výsledek po sjednocení lze filtrovat operátorem having.
select * from e in Globe.zamestnaci group by nizky: e.plat < 10000, stredni: e.plat >= 10000 and e.plat < 50000, vysoky: e.plat >= 50000 výsledek je typu: bag ( struct ( nizky: boolean, stredni: boolean, vysoky: boolean, partition: bag(zamestnanec)));
OQL – operátor order by ‣
Pokud chceme jako výsledek dotazu setříděný seznam místo množiny použijeme operátor order by.
select e from e in Globe.zamestnanci order by e.jmeno, e.vek; Výsledek je typu: list ( zamestnanec );
OQL – množinové operátory ‣ ‣
Množinové operátory jsou definovány na kolekcích 'set' a 'bag'. Operátory: ‣ ‣ ‣ ‣
‣
sjednoceni (union nebo +) průnik ( intersect nebo *) rozdíl ( except nebo -) libovolný prvek ( pick )
Příklad: libovolný zaměstnanec, který není v množině moji_zamestnanci.
pick( select e from e in Globe.zamestnanci - moji_zamestnanci);
OQL – množinové operátory ‣
Inkluze ‣ ‣ ‣ ‣
‣
a a a a
< b <= b >b >= b
a leží v b, a se nerovná b. a leží v b.
Vracejí true/false
moji_zamestnanci < Globe.zamestnanci; vrací true.
OQL – konverze ‣
Konverze seznamu obsahující jeden prvek na jeden prvek. element(exp)
‣
Konverze seznamu na množinu. listtoset(list)
‣
Konverze množiny na seznam. ‣
nutno definovat uspořádaní operátorem order by.
select e from e in množina order by e.a1; ‣
Odstranění duplikátů. distinct (exp)
OQL – konverze ‣
Zploštění kolekce flatten(exp)
‣
Přetypování (typ)exp
flatten(list(set(1,2,3),set(3,4,5,6),set(7)) vrací: set(1,2,3,4,5,6,7)
OQL – práce se seznamy ‣
Získaní i-tého prvku seznamu. list(a,b,c,d) [2] – vrací c
‣
Získaní části seznamu. list(a,b,c,d) [1:2] – vrací list (b,c)
‣
Získaní prvního/posledního prvku seznamu. last( list(a,b,c,d)) – vrací d obdobně operátor first.
OQL - dodatky
Schéma class Osoba { d_String jmeno; d_Date datum_narozeni; d_Set< d_Ref< Osoba > > rodice; d_List< d_Ref< Osoba > > deti; d_Ref< Byt > zije_v; Osoba(); int vek(); void svatba(d_Ref< Osoba > chot); void porod(d_Ref< Osoba > dite); virtual d_Set< d_String > aktivity(); };
Schéma - pokračování class Zamestnanec : Osoba { float plat; virtual d_Set< d_String > aktivity(); }; class Student : Osoba { d_String rocnik; virtual d_Set< d_String > aktivity(); };
Schéma - pokračování class Adresa { int cislo; d_String ulice; }; class Dum { Adresa adresa; d_List< d_Ref< Byt > > byty; d_Ref< Byt > nejlevnejsi(); };
Schéma - dokončení class Byt{ int cislo; d_Ref dum; d_Ref je_obyvan; };
d_Set> Osoby; //Vsechny Osoby, Zamestnanci a studenti d_Set> Byty; //Vsechny byty d_Set> Volne; //Vsechny volne byty d_List> Adresar; //Byty serazene podle cisel`
Cestování mezi objekty ‣
Pro přístup k jednotlivým položkám v objektu OQL používá tečkovou notaci (stejně jako C++; lze užít i „->” ve stejném smyslu) Osoba.vek = Osoba->vek
‣
Vzhledem k tomu, že vztahy jsou realizovány pomocí ukazatelů, není problém pomocí tečkové notace adresovat i je (alespoň 1-1 vztahy, komplikovanější vztahy viz. dále) ‣
Př. Mějme objekt „p“ třídy Osoba (viz. schém a ) p.zije_v.dum.adresa.ulice Nám dá adresu ulice, kde daná osoba (objekt „p“) bydlí
Cestování M-N Vztahy ‣
Pokud bychom chtěli jména všech dětí osoby „p“, nemůžeme jednoduše napsat p.deti.jmeno, protože „deti“ je seznam ukazatelů (nic nám ovšem nebrání adresovat jedno konkrétní dítě takto: p.deti[0]. jmeno). Abychom dostali seznam jmen, musíme použít select-from-where klauzuli, podobně jako v SQL: select c.jmeno from c in p.deti výsledek je typu Bag<String>. Pokud chceme množinu (Set), použijeme klíčové slovo distinct stejně jako v SQL
Cestování - dokončení ‣
‣
Nyní máme k dispozici prostředky pro navigaci mezi jakýmikoli objekty přes jakékoli vztahy a umíme adresovat jakékoli členské položky objektu. Malý příklad: ‣ Chceme znát adresy všech dětí každé osoby v databázi (pojmenovaná kolekce Osoby obsahuje všechny osoby v databázi) select c.zije_v.dum.adresa from p in Osoby, c in p.deti
Manipulace s daty ‣
‣
Jeden z hlavních rozdílů mezi OQL a SQL je ten, že OQL musí umět manipulovat s komplexními hodnotami. Díky tomu umí OQL vytvořit „jakoukoli“ komplexní hodnotu jako výsledek dotazu anebo i uvnitř dotazu jako mezivýsledek Při konstrukci dané komplexní hodnoty OQL využívá těchto konstruktorů: ‣ ‣ ‣ ‣ ‣
Struct - struktura Set – množina (v matematickém smyslu - prvku se neopakují) Bag – uspořádání prvků jako u množiny, ale prvky se mohou opakovat Array – pole (dynamické) List – seznam (uspořádané pole, u kterého máme metody pro manipulaci – insert, delete, at d )
příklad select struct (ja: p.jmeno, moje_adresa: p.zije_v.dum.adresa moje_deti: (select struct(jmeno: c.jmeno, adresa:c.zije_v.dum.adresa) from c in p.deti)) from p in Osoby
Tento dotaz nám pro každou osobu vedenou v databázi dá její jméno, adresu a jméno a adresu všech jejích dětí. Výsledek je typu: struct {String ja; Adresa moje_adresa; Bag<struct{String jmeno; Adresa adresa}> moje_deti; }
Manipulace a objekty ‣
OQL umí také vytvářet jednotlivé objekty. Pro vytvoření objektu nějaké třídy se použije jméno třídy jako konstruktor (čili OQL umí vytvořit jen ty objekty, jejichž třídy jsou definovány v databázi). Atributy vytvářeného objektu mohou být inicializovány libovolným platným výrazem.
Příklad ‣
Vytvoříme budovu se dvěma byty ‣ Předpokládejme, že ve schématu databáze je definován typ List_apart takto: Typedef List[ > List_byt; ‣]
Dotaz pak bude mít tento tvar:
Dum( adresa: Adresa(cislo: 10, ulice: “Elm Street”), byty: List_byt(list(Byt(cislo:1), Byt(cislo: 2))))
Vyvolávání metod ‣
‣
‣
Metody objektů se vyvolávají stejným způsobem, jako se přistupuje k ostatním položkám objektu – pomocí tečkové notace. Pokud daná metoda nemá žádné parametry, vyvolání vypadá stejně, jako přístup k nějaké členské proměnné. dum.nejlevnejsi.je_obyvan.jmeno nám vrátí jméno osoby, která bydlí v nejlevnějším bytě v daném domě. „nejlevnejsi“ je přitom metoda objektu „dum“, která vrátí jako návratovou hodnotu objekt typu „Byt“ (ten nejlevnější byt). Kdyby v objektu „dum“ byla členská proměnná „nejlevnejsi“ typu „Byt“, dotaz by vypadal naprosto stejně. Pokud metoda má nějaké parametry, dávají se do závorek za jménem metody
Polymorfismus ‣
Filosofie polymorfismu u OQL je stejná jako u OOP. select p.aktivity from p in Osoby
‣
V předcházejícím příkladě se polymorfismus projevuje jednak v tom, že je v podstatě uživateli skryto, že kolekce Osoby obsahuje 3 typy objektů (Osoba, Zamestnanec a Student) a jednak v tom, že je vždy zavolána správná virtuální metoda „aktivity“ (mechanismu, který to zajišťuje se říká pozdní vazba)
Polymorfismus - dodatek ‣
‣ ‣
Navíc uživatel může v dotazu explicitně specifikovat třídu u objektu, kde nemůže být odvozena staticky. (jako to bylo u objektu „p“ v předchozím dotazu). To se děje pomocí tzv. Indikátoru třídy Interpret musí potom za běhu ověřovat, zda daný objekt je daného typu Př.: select ((Student)p).rocnik from p in Osoby where „prubeh studia“ in p.aktivity
Kompozice operátorů ‣
‣
Pro používání operátorů v OQL platí jednoduché pravidlo: operátor lze umístit kdekoliv, pokud typy jeho operandů jsou korektní. Říkáme, že pravidla pro kompozici operátorů jsou ortogonální k typovému systému V tom se OQL liší od SQL, kde toto neplatí na 100%
příklad ‣ ‣
Chceme znát jméno ulice, kde žijí zaměstnanci s nejnižším průměrným platem. Budeme postupovat krok za krokem a budeme používat define pro mezivýsledky
Příklad – pokračování ‣
1) Vytvoříme si seznam (přesněji Bag) všech zaměstnanců Define Zamestnanci as Select (Zamestnanec)p from p in Osoby where „ma praci“ in p.aktivity
Příklad – pokračování ‣
2) Roztřídíme zaměstnance podle ulic a spočítáme průměrný plat pro každou ulici define plat_map as select ulice, prumerny_plat: avg(select p.e.plat from partition p) from e in Zamestnanci group by e.zije_v.dum.adresa.ulice
‣
Výsledek dotazu má typ: Bag<struct{String ulice, float prumerny_plat;}>
Příklad – dokončení ‣
3) předchozí výsledek utřídíme podle platu define setrideny_plat_map as select s from s in plat_map order by s.prumerny_plat
‣
Typ výsledku je: List<struct{String ulice, float prumerny_plat;}>
‣
4) snadno zjistíme nejmenší hodnotu v seznamu – první prvek setrideny_plat_map[0].ulice
Příklad – jednou větou… (select ulice, prumerny_plat: avg (select p.e.plat from partition p) from e in (select (Zamestnanec)p from p in Osoby where „has a job“ in p.aktivity) group by e.zije_v.dum.adresa.ulice order by avg (select p.e.plat from partition p) )[0].ulice