Doctrine 2: DQL

Dotazovací jazyk DQL (Doctrine Query Language) je jednou z nejsilnějších zbraní Doctrine 2. Kombinuje v sobě přímočarost dotazovacího jazyka SQL a nezávislost objektové entitní vrstvy modelu. Pokud berete práci s Doctrine 2 alespoň trochu vážně, bez DQL se rozhodně neobejdete.

Seriál: Doctrine 2 (12 dílů)

  1. Doctrine 2: úvod do systému 21.7.2010
  2. Doctrine 2: základní definice entit 4.8.2010
  3. Doctrine 2: pokročilá definice entit 11.8.2010
  4. Doctrine 2: načítání, ukládání a mazání 26.8.2010
  5. Doctrine 2:stavy entit a transakce 9.9.2010
  6. Doctrine 2: asociace 23.9.2010
  7. Doctrine 2: práce s asociacemi 14.10.2010
  8. Doctrine 2: DQL 3.11.2010
  9. Doctrine 2: Query Builder a nativní SQL 18.11.2010
  10. Doctrine 2: události 9.12.2010
  11. Doctrine 2: událostní handlery 13.1.2011
  12. Architektura aplikace nad Doctrine 2 23.2.2012

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 en­tit.

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, UPDATEDELETE.

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.

Provozuje vývojářskou firmu Medio Interactive. Vystudoval informační a znalostní inženýrství na VŠE, kde stále příležitostně přednáší o tvorbě webů.

Komentáře: 42

Přehled komentářů

Jira K čemu to je dobré?
Ped Re: K čemu to je dobré?
Nox Re: K čemu to je dobré?
Jakub Vrána Položené dotazy
Ondřej Mirtes Re: Položené dotazy
Jakub Vrána Re: Položené dotazy
Ondřej Mirtes Re: Položené dotazy
Jakub Vrána Re: Položené dotazy
Koubas Re: Položené dotazy
Jakub Vrána Re: Položené dotazy
manik.cze Re: Položené dotazy
Jakub Vrána Re: Položené dotazy
manik.cze Re: Položené dotazy
Jakub Vrána Re: Položené dotazy
jos Re: Položené dotazy
Jakub Vrána Re: Položené dotazy
jos Re: Položené dotazy
drevolution Re: Položené dotazy
Jakub Vrána Re: Položené dotazy
Martin Malý Re: Položené dotazy
Jakub Vrána Re: Položené dotazy
drevolution Re: Položené dotazy
Jakub Vrána Re: Položené dotazy
Jaroslav Re: Položené dotazy
Jakub Vrána Re: Položené dotazy
okbob Re: Položené dotazy
Jakub Vrána Re: Položené dotazy
talpa Re: Položené dotazy
Jakub Vrána Re: Položené dotazy
Trpajzlík Re: Položené dotazy
Honza Marek Re: Položené dotazy
Jakub Vrána Re: Položené dotazy
Cechjos Re: Položené dotazy
manik.cze Re: Položené dotazy
Jakub Vrána Re: Položené dotazy
Cechjos Re: Položené dotazy
Cechjos Re: Položené dotazy
Ot@s Není možné vytvářet nové záznamy pomocí INSERT
Cechjos Re: Není možné vytvářet nové záznamy pomocí INSERT
backup verte mi, cisar nema skutecne zadne saty
jos Re: verte mi, cisar nema skutecne zadne saty
okbob Re: verte mi, cisar nema skutecne zadne saty
Zdroj: https://www.zdrojak.cz/?p=3358