Tvorba dotazů
Po té co jsme definovanou entitu i uložili do databáze, je na čase zabývat se tím co s ní dál. Určitě entity budeme potřebovat načítat, ale i mazat, či pracovat s jejich surovými daty. Při tom všem však již budeme potřebovat sestavovat dotazy.
Patrně jste již na nějaké dotazy narazili. Buď při zkoumání ukázkové aplikace, nebo v kapitole o vnějších relacích. A určitě vás trklo, že dotazy mají podobu pole. Je klidně možné, že u někoho se dostavit "WTF?!" efekt :-) Ale má to svou jasně danou logiku a je to nejlepší řešení. Hned si řekneme proč.
Dotazy = pole? Proč?!
Pokud má být ORM nezávislé na použitém typu backendu - tedy databáze, tak se nemůže v rámci dotazů spoléhat na žádný konkrétní SQL dialekt. To je evidentní.
Jaké jsou ale možnosti?
Jako první se nabízí nějaký univerzální SQL dialekt a ten parsovat a transformovat. Ale proč to hergot dělat? Není to rovnák na ohýbák? Vyvíjet (nebo integrogart) nějaký parser je spousta neproduktivní práce, spousta neproduktivního kódu o který je třeba se starat. Z hlediska nákladů to nemá ospravedlnění. Prostě parsovat SQL, aby na druhém konci bylo SQL a vynaložit na to prostředky (čas = peníze) nemá logiku a absolutně žádný přínos. Nehledě na to, že to znamená i režii strojového času během provozu aplikace.
Další možnost ... Co to udělat nějak objektově a poskládat dotazy formou volání metod a dotaz mít reprezentován objektovým modelem? Fajn, tak to ve skutečnosti v Jet DataModel je. Dotaz má ve skutečnosti opravdu svůj model a je reprezentován instancemi tříd. Když se podíváte do hlubin Jet DataModel, tak na to určitě narazíte. Ale je to nepoužitelné pro aplikace z reálného světa (i když to technicky použít lze). Proč? Protože velice často dotazy tvoříme, skládáme, sestavujeme. Například na základě toho jaký má uživatel filtr a na základě miliónů dalších aspektů. A pro takové použití je nějaké volání metod čehosi opravdu krajně neefektivní a nepřehledné.
Nechtěl jsem ani vyvíjet rovnák na ohýbák aby to bylo "doměle sexy". Pro mne je sexy to co je efektivní. A nechtěl jsem ani bolavou hlavu z objektového blázince, který by zrovna v tomto případě byl kontraproduktivní. Proto jsem zvolil tuto cestu a dotazy v Jet DataModel se formulují jako pole. Tedy například takto:
$where = [];
if( $search ) {
$search = '%' . $search . '%';
$where[] = [
'article_localized.title *' => $search,
'OR',
'article_localized.text *' => $search,
'OR',
'article_localized.annotation *' => $search,
];
}
if( $another_filter ) {
if($where) {
$where[] = 'AND';
}
$where[] = [
'article.id' => another_filter
];
}
//... ... ...
$articles = static::fetchInstances( $where );
Základní princip
Jak je patrné z jednoduchého příkladu výše, tak dotaz kombinuje běžné a asociované pole.
Klíče pole tvoří referenci na příslušnou vlastnost entity. Reference je tvořena takto: nazev_entity.nazev_vlastnosti. Tedy právě zde je použit názvy entit určené definicí. Název entitu je názvem jakékoliv entity, která ovšem je ve vnitřní i vnější relaci.
O provázání tabulek se starat nemusíte. To udělá Jet DataModel automaticky na základě definic relací. Prostě definice určuje jak má fungovat provázanost a v dotazech tuto provázanost jednoduše používáte.
Název vlastnosti je jasný - přesně odpovídá názvu vlastnosti třídy entity (ne sloupečku v DB tabulce - ten se může jmenovat odlišně).
Vaší pozornosti jistě neunikl znak * za referencí. To je jeden z operátorů. Na operátory se koukneme později.
Hodnota prvku pole (zde tedy prvku dotazu) je hodnota, která se má vztahovat k danému prvku dotazu. Ale je třeba si říct, že hodnota může být i pole. Backend si s tím poradí a z pole udělá například známé ... AND article.id IN ( 5, 6, 99, 100) ...
Dále může následovat logický operátor 'OR', nebo 'AND'. A ty již nejsou asociované. Tedy logické operátory přidáváme do dotazu jako do běžného pole.
V neposlední řadě je třeba říct, že dotaz se může skládat z dalších dotazů. Lépe řečeno pole se může skládat z dalších polí, které tvoří logické podskupiny dotazu. Je to to samé, jako závorky () v SQL. Pro přehlednost takto:
$where = [
[
'entity_x.property_a' => 1,
'OR'
'entity_x.property_b' => 2
],
'AND',
[
'entity_y.property_a' => 3,
'OR'
'entity_y.property_b' => 4
]
];
Operátory
A teď k těm operátorů, které mohou být za referencí. Píšu 'mohou', protože výchozí operátor = se uvádět nemusí, ale zároveň také operátor = nemusí znamenat = ve smyslu SQL, ale operátor IN, pokud je použito pole ... Ukažme si raději tabulku s přehledem všech operátorů.
Operátor | Význam v SQL |
---|---|
= (Nepovinný, výchozí - netřeba uvádět) |
Pokud navázaná hodnota není polem, tak operátor představuje běžný operátor = Pokud je navázaná hodnota pole, pak operátor představuje SQL IN( ... ) |
!= |
Pokud navázaná hodnota není polem, tak operátor představuje běžný operátor <> Pokud je navázaná hodnota pole, pak operátor představuje SQL NOT IN( ... ) |
* | LIKE '...' |
!* | NOT LIKE '...' |
> | V SQL stejný význam |
< | V SQL stejný význam |
>= | V SQL stejný význam |
<= | V SQL stejný význam |