Databázová vrstva pro Nette: dibi, Doctrine, NotORM?
- vrana
- Člen | 131
dakota napsal(a):
echo $c2c->company->country->name;
Tohle ale přece rozhodně vztah 1:1 není. Více společností bude mít stejný stát, takže při použití JOINu se bude název státu přenášet opakovaně (to sice prakticky zdržovat nebude, ale je dobré si to uvědomit).
Pár dní už jde použít select("company.country.name")
.
Mam tiež chápať aj to že: poddotaz v MySQL s niekoľko tisíc vrátenými id asi radšej nepoužívať.
Spíš takhle: „Poddotazy v MySQL raději nepoužívat.“ NotORM se
tímto pravidlem řídí a poddotazy pro MySQL překládá na
IN ($ids)
. To nijak zvlášť nezdržuje ani při velkém
množství záznamů, problém je spíš s množstvím zabrané paměti
u velkých výsledků.
- dakota
- Člen | 148
vrana napsal(a):
dakota napsal(a):
echo $c2c->company->country->name;
Tohle ale přece rozhodně vztah 1:1 není. Více společností bude mít stejný stát, takže při použití JOINu se bude název státu přenášet opakovaně (to sice prakticky zdržovat nebude, ale je dobré si to uvědomit).
Pár dní už jde použít
select("company.country.name")
.
Asi som trochu v tomto prípade ustrelil. Vyskúšal som tieto dotazy čo som uviedol ešte raz.
V druhom prípade kedy som chcel zlepšiť efektivitu pri získaní nazvu firmy (vzťah 1:1), čiže pri
$company_2_category = $db->table('company_2_category')
->where("category_id", 55)
->where('company.country_id', 2);
$company_2_category->select('company_2_category.company_id, company.id, company.company_name, company.country_id');
foreach ($company_2_category->limit(500) as $c2c) {
echo $c2c->company_name;
echo $c2c->company->country->name;
}
by sa vykonali tieto dotazy:
SELECT company_2_category.company_id, company.id, company.company_name, company.country_id
FROM `company_2_category`
INNER JOIN `company` ON `company_2_category`.`company_id` = `company`.`id`
WHERE (`category_id` = 55) AND (company.country_id = 2)
LIMIT 500
SELECT *
FROM `country`
WHERE (`country`.`id` IN (2))
Trochu ma to prekvapilo, asi sa informácia o country_id získala už zo selectu, myslel som si že sa pre zistenie country_id vykoná další dotaz kde bude v IN (500 ids).
Takže efektívne to je. Len asi nemôžem pri tom využiť cache pri nazvoch stlpcov.
Len mi pride v tomto pripade zbytočne to takto komplikovať len preto že
Nette\Database\Selector (NotORM) podporuje len takyto JOIN:
SELECT ... FROM company_2_category INNER JOIN company ON ...
a nie
aj v určitých prípadoch
SELECT ... FROM company INNER JOIN company_2_category ON ...
.
Ak nie je ochota alebo potreba rozšíriť syntax o takyto typ JOIN, tak nebudem namietať. Chcel som len ukázať aj iný pohľad.
Syntax select("company.country.name")
nemám vyskušanú.
Editoval dakota (5. 1. 2011 14:00)
- vrana
- Člen | 131
dakota napsal(a):
len malá poznámka: distinct som vyskúšal funguje v tomto prípade rovnako, ale týmto sa v podstate ignoruje GROUP BY … HAVING …, možno tie konštrukcie nie sú časté ale môžu sa vyskytnúť.
Proto tento dotaz nesestavuje NotORM. Já postupuji tak, že dotaz pro zjištění celkového počtu vymyslím sám – podle situace v něm zohledním připojené tabulky, group a having a případné další věci. Zobecnit to tak, aby tento dotaz „vymyslelo“ NotORM, si netroufám.
- vrana
- Člen | 131
dakota napsal(a):
Len mi pride v tomto pripade zbytočne to takto komplikovať len preto že Nette\Database\Selector (NotORM) podporuje len takyto JOIN:
SELECT ... FROM company_2_category INNER JOIN company ON ...
a nie aj v určitých prípadochSELECT ... FROM company INNER JOIN company_2_category ON ...
.
Jaký by se pro to používal zápis? NotORM nezná schéma databáze, takže
"$table.$column"
se musí používat vždycky pro stejnou věc –
konkrétně pro odkazovanou tabulku. Takže pro odkazující tabulku by musela
vzniknout nějaká jiná syntaxe (třeba "$table:$column"
), což
nepovažuji za šťastné.
Len asi nemôžem pri tom využiť cache pri nazvoch stlpcov.
To mě taky trápí. Už před delší dobou jsem přemýšlel o tom, že
bych povolil syntaxi $row["$table.$column"]
, ale to by byl bez
keše zabiják výkonnosti (protože by se nejdřív musel položit dotaz bez
JOINu a pak ještě znovu s JOINem). Navíc to má akademický problém v tom,
že název sloupce může obsahovat tečku, ale ten se projevuje i jinde.
Každopádně jsou JOINy něco, co jsem v NotORM nikdy moc nechtěl a jsou tam jen proto, že se s nimi některé věci dělají pohodlněji. Pokud to jde, tak já osobně se bez nich obcházím – výkonnostně se jimi nic zásadního nezíská.
- Cifro
- Člen | 245
Nedávno som čítal database-development-mistakes-made-by-application-developers (via @smashingmag). Možno sa to hodí pre inšpiráciu…
- David Grudl
- Nette Core | 8212
dakota napsal(a):
Bude sa Nette\Database príp. Nette\Database\Selector v blízkej budúcnosti rozširovať o nejaké dalšie metódy, príp. možnosti, nástroje – napr. o niektoré veci z dibi?
Zatím bych nepředbíhal. Nejprve chci dokončit a pořádně si vyzkoušet současnou podobu a pak zvažovat, co dál. Nebude existovat důvod, proč při Nette\Database používat i dibi, ale kterou cestou se vydat zatím váhám.
Je možné konkrétnejšie uviesť aká ďalšia podpora modelu sa popri Nette\Database v Nette pripravuje? V úvodnom príspevku je slabo načrtnuté čo sa v rámci podpory modelu pripravuje.
Cílem je co nejvíce zjednodušit často používané operace CRUD. Tedy víc práce bude mimo Nette\Database.
- dakota
- Člen | 148
vrana napsal(a):
Každopádně jsou JOINy něco, co jsem v NotORM nikdy moc nechtěl a jsou tam jen proto, že se s nimi některé věci dělají pohodlněji. Pokud to jde, tak já osobně se bez nich obcházím – výkonnostně se jimi nic zásadního nezíská.
Uvediem malý zjednodušený príklad použitia Nette\Database\Selector v modeli (inšpirované príkladom v Nette\Examples), týka sa problému ktorý som uviedol v predošlých príspevkoch:
class Model extends Object
{
public static $database;
public static function companies()
{
return self::$database->table('companies');
}
}
$companies = Model::companies();
$filter = $form->getValues();
if (!empty($filter['country_id'])) {
$companies->where('company.country_id', $filter['country_id']);
}
if (!empty($filter['company_name'])) {
$companies->where('company.company_name', $filter['company_name']);
}
if (!empty($filter['city'])) {
$companies->where('company.city', $filter['city']);
}
if (!empty($filter['category_id'])) {
$companies-> ???
// poddotaz ??? a čo ked v danej kategorii je 20000 firiem - problem MySQL
// INNER JOIN company_2_category ???
}
foreach ($companies->limit(50) as $company) {
echo $company->company_name.'<br>';
echo $company->city.'<br>';
...
}
V uvedenom príklade chcem poukázať na: ak by som sa chcel dotazovať na firmy podľa filtra tak takto jednoducho to v Nette\Database\Selector nepôjde a musím vymyslieť iný spôsob.
Jaký by se pro to používal zápis? NotORM nezná schéma databáze, takže „$table.$column“ se musí používat vždycky pro stejnou věc – konkrétně pro odkazovanou tabulku. Takže pro odkazující tabulku by musela vzniknout nějaká jiná syntaxe (třeba „$table:$column“), což nepovažuji za šťastné.
Napadá ma zaviesť pre tabuľky M:N konvenciu pre názov napr.
company_2_category
čiže nie company_category – odtial zistiť
potrebne cudzie kľúče pre pripojenie.
Edit:
Menná konvencia by možno pomohla v prípade
company_2_category
, ale nepokryla by všetky odkazujúce
tabuľky.
Ďalšie možnosti:
- ponechať „$table.$column“ a riešiť to v DatabaseReflection (v NotORM v Structure) – asi by sa musela definovať určita schéma databázy
- zaviesť nejakú syntax v metodách where a možno aj order a select, buď
namiesto
.
použiť niečo iné alebo zaviesť syntax v názve tabulky zapísanej v metode - doplniť metódu join – to asi nie ale v nutnom pripade by to pomohlo
- …
Možno niekoho napadne niečo iné.
Nette\Database\Selector (NotORM) v určitých prípadoch značne uľahčuje prácu, sprehľadňuje kód a je aj efektívne. Ja osobne ho používať pravdepodobne budem (namiesto dibi a DibiFluent). Ale v určitých prípadoch prácu pri zostavovaní dotazu skôr pridá na rozdiel od DibiFluent.
Editoval dakota (6. 1. 2011 9:14)
- dakota
- Člen | 148
vrana napsal(a):
Já bych neváhal použít (syntaxe NotORM):
$companies->where("id", $db->company_2_category("category_id", $filter['category_id'])->select("company_id"))
Aj vtedy keď dopredu neviem koľko firiem je v danej kategorii? Môže tam
byť 1 ale aj niekoľko desiatok tísíc. Čiže v prípade MySQL bude v IN
toľko id. S pamäťou by nemal byť až taký problém, načítajú sa
len id. Skúšal som to nie je to až také pomalé. Len obava to
používať.
Edit 10.1.2011:
Od určitého počtu firiem v danej kategórii (myslené 30000, 40000, 50000 a viac, závisí to od nastavenia memory_limit) môže byť problém s pamäťou (nebude stačiť) pri MySQL (kde je poddotaz v Nette\Database\Selector prekládaný na IN ($ids)).
Editoval dakota (10. 1. 2011 17:11)
- dakota
- Člen | 148
vrana napsal(a):
Proto tento dotaz nesestavuje NotORM. Já postupuji tak, že dotaz pro zjištění celkového počtu vymyslím sám – podle situace v něm zohledním připojené tabulky, group a having a případné další věci. Zobecnit to tak, aby tento dotaz „vymyslelo“ NotORM, si netroufám.
Count by mal zohľadňovať aktuálny stav. Ten sa može meniť
v závislosti od where, group, having a limit. Napr. v DibiFluent je to
riešené ako SELECT COUNT(*) FROM ( vytvorený dotaz ) as data
,
preto to v prip. DibiFluent nepoužívam, pretože je to neefektívne a tiež
si vytvorím vlastný dotaz na count. Implementácia count
v Nette\Database\Selector nie je zlá (pekne zohľadňuje či treba či netreba
join) až na problem pri group a having. Implementovať to aby sa dotaz na count
vytvoril správne za každých okolnosti je asi ťažké.
Edit:
Tiež si treba davať pozor a nepoužiť count bez parametru pri veľkom počte zaznamov ak ich nechcem vypisať.
Editoval dakota (6. 1. 2011 18:05)
- Cifro
- Člen | 245
dakota napsal(a):
Napr. v DibiFluent je to riešené ako
SELECT COUNT(*) FROM ( vytvorený dotaz ) as data
…
Takýto select je v prípade MySQL pomalý… Viď https://forum.dibiphp.com/…urce-a-mysql
- dakota
- Člen | 148
Cifro napsal(a):
dakota napsal(a):
Napr. v DibiFluent je to riešené ako
SELECT COUNT(*) FROM ( vytvorený dotaz ) as data
…Takýto select je v prípade MySQL pomalý… Viď https://forum.dibiphp.com/…urce-a-mysql
Ved v mojom príspevku je to uvedené ako neefektívne – takto je to zatial riešené v DibiFluent. V Nette\Database\Selector je to riešené oveľa lepšie. Ale treba to vedieť použivať a dávať si pozor na určité nejasnosti.
Editoval dakota (6. 1. 2011 17:59)
- Oggy
- Člen | 306
Jak by se dal zapsat dotaz z příkladového schématu application .. aplikace nějakého autora, které nemají tag?
pokoušel jsem se to takto ale vygenerovaný dotaz je špatný v tomto joinu:
<?php
INNER JOIN application_tag ON application.application_tag_id = application_tag.id
?>
kde si volí špatné názvy – application.application_tag_id = application_tag.id
<?php
$db->application()->where('author.born <= "1950-01-01"')->where('application_tag.tag_id = NULL')
?>
<?php
SELECT application.* FROM application INNER JOIN author ON application.author_id = author.id INNER JOIN application_tag ON application.application_tag_id = application_tag.id AND (author.born <= '1950-01-01') AND (application_tag.tag_id = NULL)
?>
- Oggy
- Člen | 306
Teď mě tak napadá … nemohlo by to být nějak takto? je tohle cesta jak podobné situace řešit nebo má Jakub nějaký jiný postup?
<?php
$applications = array();
foreach ($db->application()->where('author.born <= "1950-01-01"') as $application) {
$tag = $application->application_tag();
if($tag == NULL) $applications[] = $application;
}
?>
Editoval Oggy (8. 1. 2011 22:25)
- dakota
- Člen | 148
Oggy napsal(a):
JOINy typu INNER JOIN application_tag ON …, LEFT JOIN application_tag ON … nie sú v Nette\Database\Selector podporované.
Myslím, že by sa dalo použiť:
$apps = $db->table('application')
->where('author.born <= "1950-01-01"')
->where('application.id NOT', $db->table('application_tag')->select('application_id'));
// application.id NOT IN (...)
Nemám to však vyskúšané.
Editoval dakota (9. 1. 2011 12:54)
- vaclav
- Člen | 4
Narazil jsem na zajimave chovani v soucasne verzi u ktereho nevim jestli se jedna o bug nebo o predpokladane chovani…
Pro vyber zaznamu jen s nulovou hodnotou parent_id jsem pouzil
<?php
$tags = $db->table('tags')->where("parent_id IS NULL");
?>
vytvoreny dotaz vypada tako
SELECT * FROM `page` WHERE (name IS NULL IN (NULL))
ve chvili, kdy pouziji
<?php
$tags = $db->table('tags')->where("parent_id IS ?", NULL);
?>
tak je samozrejme vytvoren spravny dotaz
SELECT * FROM `page` WHERE (name IS NULL)
- dakota
- Člen | 148
vaclav napsal(a):
U mňa to funguje v poriadku, neviem o akú reviziu Nette Framework
2.0-dev ide, treba skúsiť najnovšiu.
V Nette\Database\Selector som chybu s IS NULL
vo where
neobjavil.
Nette Framework 2.0-dev, revízia c1c9de0 released on 2011–01–07.
Funguje to takto:
->where('contact_person_id', NULL);
->where('contact_person_id IS NULL');
// WHERE (`contact_person_id` IS NULL)
Editoval dakota (10. 1. 2011 16:22)
- vrana
- Člen | 131
Oggy napsal(a):
Jak by se dal zapsat dotaz z příkladového schématu application .. aplikace nějakého autora, které nemají tag?
Já bych to zapsal takhle (syntaxe NotORM):
<?php
$db->application("id NOT", $db->application_tag()->select("DISTINCT application_id"));
?>
<?php $applications = array(); foreach ($db->application()->where('author.born <= "1950-01-01"') as $application) { $tag = $application->application_tag(); if($tag == NULL) $applications[] = $application; } ?>
To by z databáze zbytečně přenášelo všechny aplikace.
A ještě poznámka – hodnoty neuzavírej do uvozovek, jejich význam se
dá v konfiguraci MySQL změnit na ohraničení identifikátorů. Nejlepší je
where('author.born <= ?', '1950-01-01')
.
- vaclav
- Člen | 4
Tak to je zvláštní, děje se mi to v té samé verzi. :)
dakota napsal(a):
vaclav napsal(a):
U mňa to funguje v poriadku, neviem o akú reviziu Nette Framework 2.0-dev ide, treba skúsiť najnovšiu.
V Nette\Database\Selector som chybu sIS NULL
vo where neobjavil.
Nette Framework 2.0-dev, revízia c1c9de0 released on 2011–01–07.Funguje to takto:
->where('contact_person_id', NULL); ->where('contact_person_id IS NULL'); // WHERE (`contact_person_id` IS NULL)
- Oggy
- Člen | 306
ještě mám spíš takovou prkotinu ..
do šablony si předám z databázového dotazu nějaké záznamy, říkejme jim $entries.
a pak je řádím do různých skupinek a uvádím počet záznamů.
tzn.
<?php
today: {=$entries->where('date = CURDATE()')->count('*')}
tomorrow: {$entries->->where('date = CURDATE()+1')->count('*')}
atd ...
?>
tak NOTORM genereuje dotazy, které se na sebe nabalují.. takže vlastně ke každému výše uvedenému řádku vezme předchozí dotaz a přidá další podmínky.
cesta, která mě napadla, je si před každým dotazem uložit původní entries do jiné proměné a každý jednotlivý dotaz pokládat nad těmi „čistými“ (původndími) entries .. ale děje se to samé ..
<?php
{var today => $entries}
today: {=$today->where('date = CURDATE()')->count('*')}
{var tomorrow => $entries}
tomorrow: {$tomorrow->->where('date = CURDATE()+1')->count('*')}
?>
jak to elegantně vyřešit ? :-)
- bojovyletoun
- Člen | 667
Měl bych dotaz, jak využít tečkovou notaci.
Schéma (* jsou FK):
- Tabulka člověk: id, jmeno, clovek_typ_id * – tabulka lidí
- Tabulka clovek_typ: id, jmeno – nějaká skupina, třeba žid, křesťan, ateista, budhista, šintoista
- Tabulka viewparam: clovek_typ_id ,* view_id * – obsahuje „připravené pohledy“- např budhista+šintoista
- Tabulka view: id, jmeno – obsahuje názvy pohledů, v příkladu nebude použita
Mám vstupní hodnotu view_id – budou se tedy zobrazovat např židé a
křesťané.
Lámu si hlavu s tím, jak to zapsat (plus půlhodiny hledání, proč to nefunguje
)
elegantněji.
zde je můj mezi výsledek: (hlavně ty opakované naázvy se mi
nelíbí)
$cond=$db->table("viewparam")->where("viewparam.view_id",$view);$db->table("clovek")->where('clovek_typ_id',$cond->select('clovek_typ_id'))
Psalo se zde:
/---quote
Pár dní už jde použít select(„company.country.name“).
\--
Jde to použít přímo v dotazu nebo až v iteraci?
Jak by šel daný dotaz zapsat?
Šlo by to nějak takhle:?
$cond=$db->table("viewparam")->where("viewparam.view_id",$view);
$cond->select('clovek_typ.clovek.jmeno');
nebo
$db->table('clovek')->where('clovek_typ',$cond);
Editoval bojovyletoun (17. 1. 2011 1:58)
- Oggy
- Člen | 306
lze nějak docílit LEFT JOIN nebo ho hezky obejít ?
např. dostat nějaký takovýto dotaz:
<?php
SELECT COUNT(*) FROM todo LEFT JOIN project ON todo.project_id = project.id WHERE (project.archived = 0 OR project_id IS NULL)
?>
NOTORM generuje tento dotaz:
<?php
SELECT COUNT(*) FROM todo INNER JOIN project ON todo.project_id = project.id WHERE (project.archived = 0 OR project_id IS NULL)
?>
při
<?php
{=Todo::fetchAll()->where('project.archived = 0 OR project_id IS NULL')->count("*")}
?>
cíl je dostat počet TODO, které patří k nearchivovaném PROEJKTu a nebo k žádnému projektu..
- Honza Marek
- Člen | 1664
Nemělo by se tohle vlákno zamknout? Poradna jak co napsat v NotORMu přece nepatří do diskuze o vývoji frameworku. Berte ohledy na ty, kdo maj v RSS jen tuhle sekci ;)
- vrana
- Člen | 131
Oggy napsal(a):
lze nějak docílit LEFT JOIN nebo ho hezky obejít ?
V tomto konkrétním dotazu by se to dalo vyřešit použitím
project.id
místo project_id
nebo by se dokonce celá
podmínka dala zjednodušit na 'project.archived <=> 0'
. Ale
přemýšlím, že bych podporu pro INNER JOIN vyhodil, protože to je jen
výkonnostní optimalizace a někdy může být očividně matoucí.
- vrana
- Člen | 131
Honza Marek napsal(a):
Nemělo by se tohle vlákno zamknout? Poradna jak co napsat v NotORMu přece nepatří do diskuze o vývoji frameworku. Berte ohledy na ty, kdo maj v RSS jen tuhle sekci ;)
Jsem pro. Oni sem lidi asi píšou proto, že na to tady odpovídám. Tak mě pak jen pošlete na nové místo.
- Honza Marek
- Člen | 1664
Přijde mi vhodná sekce Databáze & ORM. Takže zamykám, další dotazy na Jakuba a jeho NotORM posílejte tam.