DQL není SQL
Hned na úvod je potřeba upozornit, že DQL není žádným dialektem standardního SQL, k čemuž by vás mohla vést vzájemná podobnost. Jedná se o zcela samostatný jazyk, založený na jiných principech a ležící v úplně jiné vrstvě aplikace:
- S pomocí SQL se dotazujete na jména databázových tabulek a jejich atributů. V jazyku DQL se místo toho používají jména Doctrine 2 entit a jejich členských proměnných.
- Výsledkem SQL dotazu je vždy jedna tabulka s jednotlivými řádky a sloupci. Výsledkem DQL dotazu jsou (zpravidla) načtené instance entit.
- Při joinování více tabulek v SQL se využívají podmínky a spojovací sloupce; výsledkem je jedna tabulka, ve které je naházeno všechno dohromady. V DQL se elegantně odkazuje na definované asociace a výsledkem je více vzájemně provázaných instancovaných entit.
Například dotaz na všechny články a jejich kategorie bychom v klasickém SQL napsali takto:
SELECT * FROM article JOIN category ON article.category_id = category.id;
případně:
SELECT * FROM article, category WHERE article.category_id = category.id;
Musíme v něm použít jména databázových tabulek a atributů, explicitně joinovat přes spojovací sloupec a na výsledku budeme mít jednu velkou tabulku, kde budou vedle sebe v každém řádku najednou všechny sloupce každého článku i jeho odpovídající kategorie. Navíc se budeme muset nějak poprat s případnými stejně pojmenovanými sloupci, vyjmenovat či aliasovat je atd.
Naproti tomu v DQL se podobný dotaz definuje takto:
SELECT a, c FROM Article a JOIN a.category c
Použili jsme názvy tříd s entitami, názvy jejich členských proměnných, asociace je použita zcela přirozeně a na výsledku budeme mít instance nalezených článků, vedle nich instance nalezených kategorií, související články a kategorie budou navzájem referencovány v asociačních sloupcích.
DQL prostě patří do objektové vrstvy entit, zatímco SQL patří o vrstvu níže, k databázovým tabulkám. Doctrine 2 pak sama překládá námi napsaný DQL dotaz do příslušného SQL dotazu, který pak provede nad konkrétní databází. Stejně tak získaný výsledek zase sama promítne zpátky do konkrétních instancí entit.
Typy dotazů
S pomocí DQL můžeme provádět pouze dotazy typu SELECT
, UPDATE
a DELETE
.
Není možné vytvářet nové záznamy pomocí INSERT
. V opačném případě bychom totiž mohli zakládat úplně nové entity, aniž by o nich věděl EntityManager a IdentityMap. To by vedlo k nekonzistencím a nepořádku při správě persistovaných entit uvnitř Doctrine 2. Při praktické práci vám ale DQL inserty nebudou vůbec chybět.
Stejně tak nejsou k dispozici žádné DDL či DCL příkazy, jako CREATE
, ALTER
, DROP
, GRANT
apod. Ty totiž do aplikace využívající Doctrine 2 nepatří:
- Správně navržená aplikace by neměla za běhu jakkoliv měnit strukturu své databáze. Pokud narazíte na takovou potřebu, máte pravděpodobně špatně navrženou databázovou strukturu.
- Můžete samozřejmě vyvíjet hodně speciální aplikace, jako je webové rozhraní pro práci s databází a la phpMyAdmin nebo Adminer. V takovém případě je ale obecně nevhodné to vůbec stavět nad Doctrine 2.
Avšak i u již zmiňovaných UPDATE
a DELETE
můžete narazit na spoustu problémů. Souvisí zejména s tím, že příkazy provádí hromadné změny přímo do databáze. Zcela se obchází celý EntityManager respektive IdentityMap. Nejsou při nich nad měněnými entitami volány žádné preUpdate
či preRemove
události, nejsou následována žádná kaskádová mazání, již načtené entity zůstávají i nadále v paměti instancované a naplněné původními daty.
Proto i s UPDATE
a DELETE
nakládejte velmi opatrně a volejte je jen pokud opravdu víte, co děláte. Pro běžné použití nám tedy z celého DQL zůstává jen SELECT
.
Návrh a používání MySQL
Akademie Root vás zve na školení Návrh a používání MySQL databáze. Naučte se používat jednu z nejrozšířenějších databází – MySQL. Na školení se dozvíte vše potřebné od návrhu až po samotné využití MySQL ve vašich projektech. Školení proběhne 30.11.2010 od 10 hodin v Praze.
Základní výběrové dotazy
Základní syntaxe SELECT
dotazů v DQL je následující:
SELECT p FROM ProjectModelPerson p WHERE p.age > 20
Hlavním rozdílem od SQL je, že se místo názvu tabulky používá název třídy entity, a to i včetně případného namespace. Povinné je aliasování, v našem případě aliasujeme třídu ProjectModelPerson
na p
, můžete si ale vymyslet jakýkoliv jiný alias.
Přes alias se pak odkazujeme na danou entitu na všech ostatních místech dotazu. V podmínkách za WHERE
používáme názvy členských proměnných, takže p.age
je odkaz na členskou proměnnou age
v třídě ProjectModelPerson
.
Uvedením aliasu p
hned za příkazem SELECT
říkáme, že chceme jako výstup z dotazu získat příslušné instance třídy ProjectModelPerson
.
Jestli vás teď přepadla obava, že se takto Doctrine 2 poměrně neefektivně ptá na úplně všechny sloupce z dané tabulky, namísto abyste vyjmenovali jen těch několik málo sloupců, které zrovna v daný okamžik potřebujete, máte naprostou pravdu. Je to cena za to, že budeme v každém okamžiku pracovat s úplnými konzistentními entitami.
Doctrine 2 ale nabízí i možnost vyjmenovat jen konkrétní pole a načíst tzv. parciální objekty, které obsahují jen část dat a jsou tak ve své podstatě nekonzistentní. Pro to se používá klauzule PARTIAL
:
SELECT PARTIAL a.{id, title, text} FROM Article a
Používání parciálních objektů může znatelně vylepšit výkonnost aplikace. Zejména ve složitějších architekturách si ale načítáním parciálních objektů můžete způsobit těžko odhalitelné chyby a nekonzistence. Pokud se je tedy rozhodnete používat, musíte je mít plně pod kontrolou.
Funkce a operátory
V dotazu a podmínkách lze používat řadu obvyklých klauzulí, funkcí a operátorů, jako je LIKE
, IS NULL
, IN
, COUNT
apod. Znovu ale musím upozornit na to, že se jedná DQL, takže zde mohou být některé v SQL obvyklé funkce definovány trochu jinak nebo mohou dokonce úplně chybět.
Pro kompletní seznam všech podporovaných klauzulí, funkcí a operátorů včetně ukázek jejich použití odkazuji na dokumentaci Doctrine 2.
A pokud by vám náhodou nějaká funkce v základní nabídce chyběla, můžete si ji poměrně snadno implementovat sami.
Spojování více entit
Za klauzulí FROM
se smí uvádět vždy jedna hlavní výchozí entita, od které odvozujeme celý dotaz. Není tedy možný zápis ve stylu SELECT * FROM foo, bar
, který je ve světě SQL zcela běžný.
Namísto toho musíte jakékoliv další navazující entity připojovat výhradně s pomocí klauzule JOIN
:
SELECT a, c FROM Article a JOIN a.category c
Důležité je, že mezi takovými entitami musí být definována asociace, jinak Doctrine 2 neví, jak je propojit dohromady. V našem případě je asociace definovaná nad členskou proměnnou category
ve třídě Article
.
Pokud chceme, aby na výstupu provedeného dotazu byly již instancovány všechny nalezené články i jejich příslušné kategorie, musíme za příkaz SELECT
uvést oba aliasy a, c
. Výsledkem dotazu pak bude pole instancí všech článků, každý z nich pak bude mít ve své členské proměnné category
uloženu již načtenou instanci své příslušné kategorie.
Úplně stejným způsobem lze připojovat nejen entity za asociací 1:N, ale i opačně entity za asociacemi N:1 nebo dokonce M:N:
SELECT c, a FROM Category c JOIN c.articles a
V tomto případě se do paměti načtou úplně stejné entity, jako v případě předchozím, jenom jinak uspořádané. Výsledkem bude pole instancí všech kategorií, každá z nich bude mít ve své členské proměnné articles
pole všech článků v ní zařazených.
Pak se vám může hodit si hned explicitně definovat, podle čeho se mají dané kolekce řadit. K tomu slouží klauzule INDEX BY
:
SELECT c, a FROM Category c INDEX BY c.title JOIN c.articles a INDEX BY a.published
Query objekt
Jakýkoliv DQL dotaz se vytváří skrz EntityManager voláním metody $em->createQuery()
:
$q = $em->createQuery('SELECT a FROM Article a');
Metoda vrací instanci třídy DoctrineORMQuery
. V tento okamžik ještě nebyl dotaz proveden, pro to se musí zavolat další metoda, například:
$q = $em->createQuery('SELECT a FROM Article a'); $articles = $q->getResult();
Tento přístup umožňuje vytvořený dotaz předtím, než se provede, ještě dodatečně upravovat. Nad query objektem se dá volat celá řada zajímavých funkcí, které si podrobněji představíme v příštím díle zaměřeném na Query Builder.
Oddělení vytvoření dotazu od jeho provedení doceníte třeba v okamžiku, kdy si někde vytvoříte základní dotaz, ten si předáte do úplně jiné části aplikace, tam si jej ještě nějak modifikujete a pak jej teprve provedete. Typickým příkladem může být obecné znovupoužitelné řešení paginátoru.
Parametry
Do dotazů můžeme vkládat i proměnné hodnoty, a to buď přes pojmenované parametry uvozené dvojtečkou:
SELECT p FROM Person p WHERE p.age > :age
nebo přes poziční parametry uvozené otazníčkem:
SELECT p FROM Person p WHERE p.age > ?1
Funguje to tedy podobně, jako třeba v PDO, pouze se u pozičních parametrů místo prostého otazníčku používají šíře použitelné číslované otazníčky. Bindování konkrétních hodnot se pak provádí přes metodu setParameter()
:
$q = $em->createQuery('SELECT p FROM Person p WHERE p.age > :age'); $q->setParameter('age', 20); $persons = $q->getResult();
respektive u pozičních parametrů:
$q = $em->createQuery('SELECT p FROM Person p WHERE p.age > ?1'); $q->setParameter(1, 20); $persons = $q->getResult();
O bindování a dalších souvisejících funkcích se budeme podrobněji bavit v dalším díle zaměřeném na Query Builder.
Doporučuji vám na tomto místě podrobně projít všechny příklady různých dotazů uvedené v dokumentaci Doctrine 2.
Pokračování příště
V příštím díle budeme v tématu pokračovat. Ukážeme si všechny možnosti, jak vytvořený DQL dotaz provést a získat z něj kýžené výsledky v různých podobách. Představíme si Query Builder, který nabízí možnost snadno definovat a upravovat připravený dotaz.
Přehled komentářů