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ů)
- Doctrine 2: úvod do systému 21. 7. 2010
- Doctrine 2: základní definice entit 4. 8. 2010
- Doctrine 2: pokročilá definice entit 11. 8. 2010
- Doctrine 2: načítání, ukládání a mazání 26. 8. 2010
- Doctrine 2:stavy entit a transakce 9. 9. 2010
- Doctrine 2: asociace 23. 9. 2010
- Doctrine 2: práce s asociacemi 14. 10. 2010
- Doctrine 2: DQL 3. 11. 2010
- Doctrine 2: Query Builder a nativní SQL 18. 11. 2010
- Doctrine 2: události 9. 12. 2010
- Doctrine 2: událostní handlery 13. 1. 2011
- 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 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ěkně a přehledně zpracovaný článek a vlastně i celý seriál, jak jsem dnes náhodně objevil. Mám jen jednu mírně OT poznámku, neboť mi to leží v hlavě: K čemu to je dobré?
Není to trochu krok stranou od rozšířených standardů (třeba klasického SQL, T-SQL, PL/SQL)? Kde se to dá využít? Kolik firem to používá? Jaké může být praktické uplatnění odborníka na Doctrine, příp. DQL, ve srovnání s rozšířenějšími systémy? Vyplatí se investovat do „hraní“ si s takovým systémem?
Je to dobre k tomu, ze to obvykle vede k jednodussimu kodu, co obvykle vede ke kratsimu casu vyvoje/udrzby/navrhu, co vede ke snizeni nakladu.
K cemu to neni dobre: naopak to vede k dalsi vrstve abstrakce, co vede k vyssim narokum na HW zdroje.
V dnesni dobe casto plati ze naklady na vyvoj jsou radove vyssi nez naklady na provoz HW, takze se to vyplati (a taky kdyz se casem ukaze ze naklady na provoz neumerne rostou, tak je pravdepodobne ze projekt vydelava dostatecne zajimave penize ktere jde investovat do dalsi optimalizace a porad tak zustat na efektivnim ROI).
Ted asi zustane otazka jak to tedy setri ty naklady na vyvoj. Na dvou frontach, za prvy programator nemusi jit na uroven pouzite DB, takze nemusi resit drobne odlisnosti ruznych SQL, aplikaci nad Doctrine lze s malou mirou snahy vyvijet tak ze si dlouho udrzi prenositelnost mezi ruznymi DB (ta se obvykle ztrati az s optimalizaci na vykon, kde uz se vyplati zvolit konkretni DB a vyuzit jeji specializovane nastroje). Za druhy je to dalsi vrstva abstrakce, takze nemusite uvazovat jak vybrat z tabulky „artikly“, jejich kategorie a jeste informace o autorovi a vzpominat jak je to v DB propojene a pak to predelavat ve chvili kdyz zmenite DB schemu. Proste vyberete ty 3 objekty pres vazbu definovanou v objektech a dal pracujete s instancemi objektu. Jestli pak treba vnitrne predelate jak je artikel prepojeny s autorem (treba z 1:1 na 1:N aby autoru mohlo byt vice), puvodni DQL dotaz a navazujici kod se zmeni jen minimalne a docela prirozene.
Investovat do DQL pokud umite SQL mi prijde jako zbytecny dotaz, nelibi se mi ten perex clanku, ze jde o neco uplne jineho, naopak bych rekl ze je to v podstate to same az na par drobnych detailu. Ten hlavni rozdil je v te vrstve, SQL je primo nad DB, DQL je nad definovanymi objekty.
Spis je otazka jestli investovat do nauceni nejakeho ORM. Ja myslim ze se to urcite vyplati, ne ze by to byl vselek pro vsechny typy aplikaci, ale i kdyby jste to nikdy nepouzil, znat ORM „styl vyvoje“ dava zpetne dobry stupen nadhledu i pri tvorbe klasickeho SQL a navrhu DB schemat.
Nechápu – naučení DQL je otázka max. 1 odpoledne i pro podprůměrně inteligentního člověka se znalostí SQL…skoro se není co učit.
Je to pouze jiný způsob načtení entity, dá se takto načíst skupina entit najednou a šetřit výkon.
Proto nějaké otázky ohledně investic a využívanosti jsou irelevantní – i ten kdo nezná DQL a zná SQL a není úplná guma ten kód okamžitě pochopí
Toť (asi zjednodušeně) vše…
Možná předbíhám do dalšího dílu, ale zajímaly by mě dvě věci:
1. Jde nějak určit, přes který sloupec se provede spojení? Pokud jsou např. v tabulce Article sloupce Created_by a Updated_by, oba s vazbou na Person, tak jak určím, přes který se spojení má provést?
2. Jaké se položí dotazy? Když spojím třeba tři tabulky (Article, Article_tags a Tags), budou se přenášet všechna data Article (tedy třeba i kompletní text článku) u každého řádku výsledku (tedy pro každou značku) znovu?
Ptám se i proto, že mi DQL přijde ve srovnání s mým NotORM neuvěřitelně neohrabané a podle všeho i neefektivní.
1) To se určuje v dotazu u klauzuje JOIN, Honza to popisuje i v článku:
Takže v tvém případě:
Podotknul bych ale, že v případě DQL to určuje pouze, jak bude entita naplněná pomocí eager loadingu. Pokud bys žádný JOIN neprovedl a pak zavolal `$article->getCreatedBy()`, Doctrine provede další dotaz na asociaci do DB v rámci lazy loading.
2) Pokud nezvolíš partial dotaz, pak ano. Načítají se zkrátka celé entity (bez navázaných asociací, viz první bod), protože ten dotaz tak položíš a tudíž se předpokládá, že celou entitu v konzistentním stavu chceš.
NotORM je nástroj na něco jiného. V něm se právě povinnosti psát nějaké stringy, které se pak přeloží na dotaz do DB, zbavuješ a necháš na nástroji, aby ti je generovalo samo. V DQL tyto stringy naopak psát chceš. Pokud ne, nepoužiješ DQL, ale nějakou z metod EntityManageru. Takže bych to neporovnával.
1. Omlouvám se za nepozornost a děkuji za nasměrování.
2. Já chci načíst celé entity, ale nechci, aby se přenášely víckrát. Existuje v DQL nebo v celém Doctrine nějaký způsob, jak databázi položit konstantní počet dotazů (tedy rozhodně nechci v každém průchodu cyklem pokládat další dotaz) a přitom nepřenášet data opakovaně (tedy rozhodně nechci text článku přenášet s každou další značkou)?
Nad DQL se pozastavuji proto, že podle mě jde z celého procesu eliminovat. Nevidím žádný případ, na který bych ho měl používat, a ani důvod, proč bych ho vůbec měl potřebovat.
2) Nevím, jestli jsem tě pochopil správně, ale pokud si v Doctrine načteš z databáze nějakou entitu, Doctrine si ji uloží do IdentityMap a v rámci jednoho requestu, resp. života EntityManageru, pokud si o tu samou entitu zažádáš znova (ať už dotazem na asociaci, nebo $em->find($id), sáhne se po její referenci tam a nevolá se databáze.
Pro zkoumání, jaké Doctrine pokládá dotazy, si ji doporučuji zprovoznit v Nette s Doctrine2Panelem (http://addons.nette.org/cs/doctrine2panel) a napsat si jednoduchou aplikaci s pár entitami a vyzkoušet si, jak se to chová při iterování atd.
Pokud se lazy-loading v Doctrine zblázní a začne v cyklu pokládat dotaz při každém průběhu, je to právě chvíle pro DQL a použití JOIN, který načte navázané asociace pro všechny načtené entity v rámci jednoho dotazu.
DQL poskytuje také alternativní (http://www.doctrine-project.org/projects/orm/2.0/docs/reference/dql-doctrine-query-language/en#select-queries:dql-select-examples) zápis filtrování (http://www.doctrine-project.org/projects/orm/2.0/docs/reference/working-with-objects/en#querying:by-simple-conditions) a jediný zápis řazení.
Já chci udělat tohle:
Chci, aby to položilo maximálně tři dotazy (klidně i jenom dva) a aby se text článku nepřenášel opakovaně. Dá se to dokázat pomocí DQL nebo Doctrine obecně?
1) ve třídě Article si nadefinuješ dvě many-to-one vazby na Person, jednu namapuješ na členskou proměnnou createdBy a druhou na updatedBy. V DQL potom rozhoduje, jestli po JOIN použiješ a.createdBy nebo a.updatedBy – interně se tedy joinuje podle sloupce, namapovaného na tu konkrétní proměnnou ve třidě Article (zřejmě jsi špatně pochopil, že „person“ v „a.person“ značí třídu, ve skutečnosti je to název proměnné ve třídě Articles).
2) SELECT t FROM Tag t LEFT JOIN t.articles a
je tohle neohrabané? :) Navíc se nemusím vůbec starat o vazební tabulku, kterou si Doctrine obhospodařuje sama.
Dokud za SELECT nedáš „t,a“, tak se z tabulky Articles joinuje pouze sloupec, přes který je svázán s vazební tabulkou. Pokud pak chceš získat z vrácených instancí Tagů jejich články, jednoduše si je vytáhneš z jednotlivých instancí pomocí $tag->getArticles() a teprve až u nějaké instance článku použiješ nějakou metodu, např getter, tak se lazy loadují jeho data (ve skutečnosti totiž neobdržíš přímo instanci třídy Article, ale automaticky generovanou ArticleProxy, o čemž se asi ještě bude psát, nebo psalo). Navíc každý unikátní článek se načte jen jednou – entity manager si udržuje seznam již načtených entit.
Nepřipadá mi to neefektivní, ještě by se možná daly v rámci optimalizace po získání tagů ručně před-načíst všechny potřebné články pomocí SELECT a FROM Article a WHERE id IN (seznam_idcek_clanku), aby se při lazy-loadování nevolala spousta dotazů na jednotlivé články, ale na to už se asi každej vykašle :)
Aha, takže když chci dotazy pokládat efektivně (tedy ne při každém průchodu cyklem a nepřenášet stejná data opakovaně), tak mi s tím Doctrine nijak nepomůže a to ani když se uchýlím k DQL. Jediná možnost je udělat si to ručně skládáním seznamu IDček a přednačítáním článků.
Jaká je best-practice pro tuto primitivní úlohu (načíst třeba prvních deset článků a k nim všechny jejich tagy)? Mám na to využít DQL nebo ne? Bohužel tuším, že řešení bude kompromis mezi efektivitou a elegancí.
Nevím jestli nechápu pořádně tvůj dotaz, nebo ty odpověď, kterou napsal Koubas, zkusím to tedy na příkladu, který si dával výše:
Dotaz se tedy provedl jen jednou na začátku a nadále už pracujeme jen s vrácenými entitami bez dalších dotazů.
Položí se tedy jen jeden dotaz? Mohl bys ho sem prosím uvést? Rád bych si potvrdil tušení, že když má článek třeba deset značek, tak se celý text článku přenáší desetkrát.
A ještě by mě zajímalo, jestli by se to dalo rozšířit o nějaký sloupec ze spojovací tabulky (třeba důležitost značky pro daný článek). Tabulka article_tag by tedy měla tyto sloupce: (id_article, id_tag, priority). A podle toho priority bych to třeba chtěl seřadit a ve výstupu také zobrazit.
Doctrine se defaultně ptá na všechny sloupce entity, takže je všechny po jednom vyjmenovává, dotazy jsou tedy většinou hodně dlouhé. Nemám to u sebe naprogramované, ale z analogické situace usuzuji, že by dotaz vypadal nějak takto:
SELECT t0.id as id0, t0.content AS content0, …. , t1.id AS id1, t1.name AS name1, …. FROM article t INNER JOIN article_tag t2 ON t0.id = t2.article INNER JOIN Tag t1 ON t1.id = 2.tag
S tím pojmenováním těch tabulek písmenky si nejsem jistý jak to generuje, mám to tu asi jinak než by to D. udělala, ale to teď nehraje roli.
Takže myslím, že máš pravdu, že se článek přenese pro každý najoinovaný řádek. Jestli existuje nějaká optimalizace to nevím, asi by to mohlo jít přes ty parciální dotazy (nejdříve si načíst články a pak k nim dotáhnout pouze entity tagů), ale jak konkrétně by to vypadalo teď bohužel nemám čas zkoumat.
Další příklad už jsem taky rozepisovat nebudu dopodrobna, myslím, že by to bylo složítější – jakmile bys ke spojovací tabulce chtěl přidat ten další parametr, tak pro tuto tabulku budeš muset udělat další entitu, která bude mít dvě asocoiace a tu tvojí hodnotu.
Děkuji za čas, který jsi mi věnoval.
Já moc nerozumím tomu, proč bych měl používat systém, který mi šíleně svazuje ruce, a když už mi něco dovolí udělat, tak se musím uchýlit k ručnímu psaní dotazů, které se stejně provedou neefektivně.
jestli se ti tady někdo pokusí znova zodpovědět ten dotaz aniž by ho pochopil, tak ho prosím už pošli do … no … někam
V mém poděkování nebyla špetka ironie, opravdu jsem Vaškovi vděčný. A myslím, že můj dotaz pochopil přesně.
Vašek: Nevím jestli nechápu pořádně tvůj dotaz, nebo ty odpověď, kterou napsal Koubas
mě přišlo, že si Koubasuvo odpověď pochopil, takže ten XOR ukazuje na Vaška )
Já myslím, že hlavní motivací je to, že ti tenhle systém (Doctrine2) ve většině případů dává zapomenout na to, že s nějakou databází vůbec pracuješ a poskytuje ti prostě ukládání a načítání entit. Tenhle přístup se občas hodí, občas zase svazuje ruce.
Jakékoliv ORM nemá s efektivitou provádění dotazů co dělat. Nikdy to nebude tak efektivní jako SQL dotaz postavený pro konkrétní situaci.
To beru. Ale když to není efektivní, tak bych aspoň chtěl, aby to bylo elegantní. Abych mohl snadno vyjadřovat operace, které chci s daty udělat. Podle ukázek to ale spíš vypadá tak, že se operace vyjadřují dost krkolomně (mě vadí už jenom to, že na některé operace musím používat DQL) a stejně se provádějí pomalu.
NotORM je toho pravý opak – dovoluje elegantně získat data pomocí efektivních dotazů.
Počkej, Jakube – zajímá tě to řešení v Doctrine a důvody, proč to je tak řešené, nebo hledáš něco, k čemu bys mohl dopsat „NotORM to dělá mnohem líp!“? :) Protože jestli máš tu touhu vysvětlovat, proč to NotORM dělá jinak a v čem je to podle tebe lepší než jiné používané metody, tak máš dveře otevřené… ;)
Já bych chtěl Doctrine v první řadě trochu poznat. Říkám si, že když je z toho tolik lidí na větvi (v dobrém slova smyslu), tak to musí být pecka. Že v tom půjde psát přehledný a dobře udržovatelný kód, který nebude zbytečně ukecaný (čím víc kódu, tím víc chyb) a navíc bude v ideálním případě i efektivní.
A říkám si, že když je to tak velký projekt, tak určitě musí mít tyhle věci perfektně zvládnuté. A ptám se, protože si myslím, že jsem třeba jen něco přehlédl – že ten krkolomný kód, který se vykonává neefektivně, půjde napsat určitě nějak líp, než napadlo mě. Bohužel tomu ale nic nenasvědčuje.
Zatím si na srovnání určitě netroufnu, protože znám Doctrine málo. Až ho poznám lépe, tak to klidně můžu zkusit.
Práce s entitami jako je:
a provádění toho samého na úrovni asociovaných entit mi elegantní určitě přijde. PHP programátorovi se to píše hezky a dokonce to i hezky vypadá. Jakmile se ale kód dostane nad trochu větší data, tak může způsobovat vrásky databázistům. Většina dotazů se v Doctrine dynamicky generuje, na což se špatně trefuje s indexy. Aby měl člověk alespoň trochu kontrolu nad tím, jaké SQL se bude provádět, na to mu slouží právě DQL. Tak to alespoň chápu já.
Zrovna tyhle čtyři operace mi přijdou tak triviální, že se snadno píšou i v prostém SQL. Z toho důvodu v NotORM původně ani nebyly. Pak jsem pro ně jednoduchou obálku přeci jen dodělal. Takže jestli tohle je silná stránka Doctrine, tak mě to moc nepřesvědčí.
Mě přijde zajímavé hlavně získávání dat, kde se zcela běžně pracuje s mnoha tabulkami a potřebuji nějak šikovně vyjádřit, co se s výsledkem pak má udělat. A chci, aby toto šikovné vyjádření bylo inteligentní – abych mu nemusel radit, jak má postupovat.
NotORM je nepoužitelný, už jenom kvůli tvýmu názoru na vyjímky – PHP4 už tu s námi dávno není.
Za prvé nechápu, jak s NotORM souvisí můj názor na výjimky, ale pro jistotu ho shrnu: „Když se pro zpracování chyb použijí výjimky, tak je nezbytně nutné je na správném místě ošetřit a nenechat je probublat zbytečně vysoko. Někdy může být jednodušší místo výjimek indikovat chybu návratovou hodnotou a PHP chybou.“
Za druhé nechápu, jak NotORM souvisí s PHP 4 – NotORM vyžaduje PHP ve verzi alespoň 5.1 a naplno využívá jeho možností.
A za třetí uvedu, že NotORM respektuje způsob zpracování chyb nastavený v PDO – ten je u aplikací vhodné nastavit právě na výjimky.
Tady s tebou Jakube moc nesouhlasím. NotORM je efektivní z jednoho určitého pohledu, který tu rád propaguješ a je efektivní pro určitý vzor – který bych třeba já skoro nikdy nepoužil, minimálně bych jej neoznačoval jako efektivní.
1. Nedá se moc snadno najít scénář, pro který by NotORM bylo řádově pomalejší než nejefektivnější řešení. Někdy to může být o pár procent, prakticky ale nehrozí, že by NotORM „ulítlo“, jako to je běžné v Doctrine. Pokud nějaký takový scénář znáš, rád se s ním seznámím.
2. Od vydání článku zde na Zdrojáku se NotORM dále vyvíjí, takže je pro určité scénáře možné elegantně využít i spojování tabulek.
Jakube nic proti tobe a NotORM, protoze se mi jako celek libilo, ale trochu jsem si s nim hral a videl i streva a zpusob jakym si obcas ziskaval DDL to byla taky kapitola sama pro sebe:) a nehlede na to ze tvuj pohled na databazi obcas nebyl az tak kosher, radeji pokladas dva dotazy abys nemel duplicitni hodnoty, no a vidis, Je to zas na ukor databazoveho stroje.
Proste si nevyberes.
Můžeš být prosím konkrétnější? Co máš na mysli „zpusob jakym si obcas ziskaval DDL“?
Pokládání více dotazů je vědomé rozhodnutí, které někdy databázi může prospět a někdy ne, viz http://www.notorm.com/#performance – asi nejdůležitější je, že tento způsob kladení dotazů je velice přátelský k tomu, jak funguje keš v MySQL, což databázi samozřejmě nesmírně pomáhá.
Ano a tímto způsobem se rodí pojídači koláčů a místo jednoho SQL dotazu, co vytáhne potřebná data jedním vrzem se položí např. 94 SQL select dotazů. Ať žije lazy loading… Osobní zkušenost s opravováním aplikace nad Doctrine 1.2 … Složitější věci jako intersect/except + vnořené dotazy nad množinou dat jsou fakt zážitek v DQL. A věc zvaná NativeSQL, nemá pomalu s SQL taky moc společného, zase podřízení DQL. A používání klíčových slov jako CURRENT_DATE a pod přestavuje problém … Nutnost vytváření ID sloupce, který potřebuje Doctrine k životu? A skutečně stojí zato, pustit si logování SQLek co Doctrine vygeneruje …
Možná Doctrine 2.0 je lepší, ale 1.2 mě neskutečně pije krev ….
2) Já nevim, ale já DQL chápu jen jako jazyk pro popis toho, co chci vydolovat za data. Že chci načíst články a k nim rovnou kategorie. To, jestli se to provede pomocí jednoho nebo dvou dotazů, bych považoval za implementační detail, se kterým nemá DQL nic společného. Takže pokud bys dokázal Jakube Doctriňákům obhájit svoje řešení, pošli jim patch :)
Pomocí DQL říkám, co se má vytáhnout za data, a pak ještě někde musím říct, co se s těmito daty má udělat. Druhý krok je nevyhnutelný, ten určuje logiku aplikace, ale bez prvního kroku bych se nejraději obešel. Já nevidím smysl v opravování DQL, protože podle mě to je zbytečná vrstva, která by neměla být potřeba. Já nechci tupý systém, kterému musím pracně říkat, jaká si má vytáhnout data (a i tak to ještě udělá neefektivně). Chci chytrý systém, který si data dokáže vytáhnout sám (ideálně efektivně).
…tak jednoduše Doctrine2 není zatím pro vás, co na tom dále řešit (*); jsou prostě programátoři, kteří s „tupými“ knihovnami rádi pracují. :) (* Krom posunutí Doctrine2 někam dál k implementacím ORM i z jiných jazyků – třeba Hibernate má „batch loading“, což vaši[ oprávněnou] výtku k počtu dotazů řeší.)
DQL ale vůbec používat nemusíš – není to ani standartní způsob. Obyčejně se používají jen repozitáře, o kterých tu Honza už určitě mluvil. DQL je pouze způsob, jak můžeš Doctrine lépe říct, co má kdy načíst. Pokud to neuděláš, tak důsledně dodržuje lazy loading a tzn., že když někde vypisuješ něco v nějakém foreach, tak by to typicky položilo spoustu dotazů.
… což je v praxi málokdy přípustné, takže se použití DQL často nevyhnu.
Sice jsem nepsal o DQL, ale ad repositories – ve kterých na DQL stejně dojde, nebudou-li stačit metody pro nalézání entit. To, že na něj dojde, ale neberu jako minus; nevadí mi a naopak DQL beru jako standardní způsob pro získání kolekcí, na které základní repozitáře nestačí. (Koneckonců i Doctrine2 QueryBuilder staví DQL dotazy (což už se mi tolik nelíbí, ale pokud je to tak rychlejší než naopak, pak budiž).)
Aneb jde spíše o to, že existuje nějaké DQL, ale že Doctrine2 nepokládá zrovna ideální dotazy / neposkytuje způsob, jak dotazování více ovlivnit. // Ale samozřejmě chápu, že se D2 stále vyvíjí, takže se třeba ještě něco v tomhle směru změní.
Ještě ad Hibernate, aby bylo vidět, jak to řeší on:
http://docs.jboss.org/hibernate/core/3.3/reference/en/html/performance.html#performance-fetching-batch
Eh, oprava: „Aneb nejde ani tak o to, že…“
Tomu se snad nechce ani věřit. :-D Tak jak se to tedy řeší (omlouvám se, ale jsem líný kouknout do dokumentace)?
…pomocí persist. Potřebuje-li člověk vložit opravdu hodně řádků, musí během cyklu předešlé objekty uvolnit.
http://www.doctrine-project.org/projects/orm/2.0/docs/reference/batch-processing/en#bulk-inserts
kdysi davno, je to uz 40 let se musel zavolat programator, kdyz chtel nekdo neco z databaze vytahnout. Ten pracoval podle jednoho stejneho principu. Zacal cist ve smycce nejake recordy z databaze podle pocatecnich kriterii a kdyz se mu nejaky zaznam libil, tak si nacetl potrebne atributy a kdyz to bylo nutne, tak podle atributu otevrel nejakou dalsi tabulku , zadal pocatecni hodnoty a ve smycce cetl jednotlive recordy. Kdyz se mu nejaky libil …
To bylo samozrejme nepekne, malo flexibilni atd. blablabla ..
Proto se vymyslel dotazovaci jazyk, ktery jak cteme i zde je ‚primocary‘ a ‚lidsky‘, ze data z databaze vytahne i uklizecka. Tento jazyk je navic standardizovan a u vsech databazi stejny, neni-liz pravda. A protoze je tak primocary, tak proto se poradaji jiz 40 let skoleni, na katedrach informatiky se tato primocarost probira 2 semestry a nakonec se ty ‚samozrejmosti‘ predvedou na zkousce. Jen tak z legrace se miliony lidi denne dotazuji na internetu v databazovach diskuzich a forech, jak nejakou tu ‚samozrejmost‘ udelat.
A protoze je to tak vsechno jasne a primocare, tak se to vse zaneslo i do programatorske praxe, aby se programatori aplikaci nemuseli zabyvat pozadavky zakazniku (jak nudne ) nybrz optimalizaci dotazu.
A protoze je to vse tak jasne a primocare, tak se zavede dalsi abstraktni vrstva , ktera to vse jeste udela srouimitelnejsi a primocarejsi. A pak je take kazdemu jasne, ze neni mozne udelat insert, protoze EntityMap o tom nic nevi – to je jasne.
Vazeni, verte mi, ten cisar je skutecne nahý.
krásně si to napsal, jen co je pravda
někam si to uložim a budu tě citovat, jestli ti to nevadí
Možná na katedrách informatiky se SQL učí 2 semestry, nicméně já jej za odpoledne vysvětlím i uklízečce – což se např. o C nebo Basicu říci nedá. Bohužel řada programátorů nevěnovala SQL ani to odpoledne a raději věnovali několik stovek životů práci na bazmekách, které umožňují návrat k tomu starému a dávno překonanému principu a sladce snít o tom, že tabulka a pole jedno jsou.
Nezapomínejte na to, že miliony lidí se dnes a denně ptají jak udělat i primitivní iteraci v C nebo C++ a bohužel a v tom máte pravdu – řada vývojářů pracuje s něčím o čem vůbec netuší, čemu nerozumí a co by používat neměla – nejde jen o SQL – např. C, C++, C#, java, …