Přejít k navigační liště

Zdroják » Databáze » Doctrine 2: DQL

Doctrine 2: DQL

Články Databáze, PHP

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.

Nálepky:

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.

Komentáře

Subscribe
Upozornit na
guest
42 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
View all comments
Jira

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?

Ped

Je to dobre k tomu, ze to obvykle vede k jednodussimu kodu, co obvykle vede ke kratsimu casu vyvoje/udrzby/na­vrhu, 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.

Nox

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…

Jakub Vrána

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í.

Ondřej Mirtes

1) To se určuje v dotazu u klauzuje JOIN, Honza to popisuje i v článku:

SELECT a, c FROM Article a JOIN a.category c 

Takže v tvém případě:

SELECT a, p FROM Article a JOIN a.createdBy p
SELECT a, p FROM Article a JOIN a.updatedBy p 

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.

Jakub Vrána

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.

Ondřej Mirtes

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í.

Jakub Vrána

Já chci udělat tohle:

foreach ($db->article() as $article) { // projít všechny články
    echo "$article[content]n"; // zobrazit jejich text
    foreach ($article->article_tags() as $article_tags) { // projít všechny jejich značky
        echo "t" . $article_tags->tag["name"] . "n"; // a zobrazit jejich název
    }
} 

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ě?

Koubas

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_clan­ku), aby se při lazy-loadování nevolala spousta dotazů na jednotlivé články, ale na to už se asi každej vykašle :)

Jakub Vrána

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í.

manik.cze

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:

$q = $em->createQuery('SELECT a, t FROM Article a JOIN a.tags');
// ted se polozi jeden dotaz, ktery vrati entity Article,
// ktere v sobe jiz budou mit nactene svoje tagy (opet kompletni - tzn, i s nazvem)
$articles = $q->getResult();
foreach ($articles as $article) { // iterujeme nad entitami
    // vypisujeme text clanku (volame Nette properties nebo gettery)
    echo $article->content . "n";
    // iterujeme nad tagy, ktere souvisi s danym clankem (jsou v jeho kolekci)
    foreach ($article->tags as $tag) {
        echo "t" . $tag->name . "n"; // a zobrazime jejich nazev
    }
} 

Dotaz se tedy provedl jen jednou na začátku a nadále už pracujeme jen s vrácenými entitami bez dalších dotazů.

Jakub Vrána

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.

manik.cze

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.

Jakub Vrána

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ě.

jos

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

Jakub Vrána

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ě.

jos

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 )

drevolution

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.

Jakub Vrána

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ů.

Martin Malý

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é… ;)

Jakub Vrána

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.

drevolution

Práce s entitami jako je:

  • vytváření
  • aktualizace
  • mazání
  • odpojování a opětovné připojování

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á.

Jakub Vrána

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.

Jaroslav

NotORM je nepoužitelný, už jenom kvůli tvýmu názoru na vyjímky – PHP4 už tu s námi dávno není.

Jakub Vrána

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.

okbob

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í.

Jakub Vrána

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.

talpa

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.

Jakub Vrána

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á.

Trpajzlík

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 ….

Honza Marek

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 :)

Jakub Vrána

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ě).

Cechjos

…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ší.)

manik.cze

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ů.

Jakub Vrána

… což je v praxi málokdy přípustné, takže se použití DQL často nevyhnu.

Cechjos

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

Cechjos

Eh, oprava: „Aneb nejde ani tak o to, že…“

Ot@s

Tomu se snad nechce ani věřit. :-D Tak jak se to tedy řeší (omlouvám se, ale jsem líný kouknout do dokumentace)?

Cechjos

…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

backup

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ý.

jos

krásně si to napsal, jen co je pravda

někam si to uložim a budu tě citovat, jestli ti to nevadí

okbob

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, …

Enum a statická analýza kódu

Mám jednu univerzální radu pro začínající programátorty. V učení sice neexistují rychlé zkratky, ovšem tuhle radu můžete snadno začít používat a zrychlit tak tempo učení. Tou tajemnou ingrediencí je statická analýza kódu. Ukážeme si to na příkladu enum.