Přednáška Nette\Database na poslední sobotě
- hrach
- Člen | 1838
- video: http://www.youtube.com/watch?…
- slajdy: http://public.skrasek.com/…a_2012_04_28
- moje dodatecne poznamky k datazum:
„Pokud pridruzene related zaznamy vyberu zvlast, existuje mozna
nekonzistence, muze dojit napr. k odmazani zaznamu“
Na tuto otazku jsem reagoval, ze nette to neresi. Po hlubsim zamysleni dochazim
k zaveru, ze to ani resit neni treba. Prakticky se jedna pouze o pripad, kdy
nejaky seznam byl filtrovan podminkou na pritomnost pribuznych deti. Priklad
se slidu:
$auhtors = $connection->table('author')->group('author.id', 'COUNT(book:id) > 1');
foreach ($authors as $author) {
// zde opravdu nemam zajisteno, ze se vrati nejake knihy, dane knihy uz mohly z db zmizet.
foreach ($author->related('book') as $book) {
}
// mozno resit treba takto
$books = $author->related('book');
if (count($books) === 0) continue;
$author->title;
foreach ($books as $book) {
$book->...;
}
}
Reseni je vyse uvedene, osobne bych ale neresil, pripadne resil opravdu v kritickych castich aplikace.
„Ralacni vazby jsou lazy a jsou volany casto az v sablone, pokud
vypadne spojeni, dojde k rozbiti sablony.“
Netyka se uplne Nette\DB. Mozne reseni pres obcache. Stejnym neduhem trpi
v Nette napriklad komponenty. A to uvnitr komponent muzeme k databazi
pristupovat sebecisteji pres modely. Osobne v tom ale nevidim zadny zavazny
problem.
Na zbyvajici dotazy jsem myslim odpovedel kompletne snad uz behem prednasky. Pokud mate jakykoliv problem, nebojte se zeptat zde na foru, zalozte nove vlakno, snazim se vsemu venovat.
- marek.dusek
- Člen | 99
K tomu „zmizeni“ deti mezi vyberem jejich ID a naslednym vyberem vlastnich dat
Cela problematika konzistence (atomicity) mezi vice dotazy je pomerne slozita a vzdy je nutne posuzovat konkretni priklady – kdyz nekdo namitne, „co kdyz mezitim nekdo knizku uplne smaze“, opacim, ze to se v me aplikaci nestane, protoze nic nemazu. Pokud rekne „co kdyz tu knizku nekdo priradi jinemu autorovi“, reknu, ze (pokud resim problem, ze mam predvybrano jeste ID „stareho“ autora), to uzivatel uvidi proste stejn, jako kdyby kliknul o pulvterinu driv, kdy to tak jeste bylo … atd ;)
Ony totiz existuji i mnohem jednodussi priklady, kdy je „neatomicita“ problem – treba obycejne kombo „nejdriv count, ktere mi urci strankovani, pote vyber daneho vyseku dle limitu“ – co kdyz mezi countem a vyberem dojde ke zmene. To je vec, kterou nelze rozumne resit vubec nijak, krome vynuceni transakce se serializovanou izolaci (pokud to dana db vubec podporuje).
TL;DR
Nekonzistence mezi vice SQL typicky neni problem a krajni pripady (kdy to vubec muze nastat / je to realny problem) si clopvek vzdy musi osetrit sam, bez ohledu na pouzitou DB vrstvu.
- bojovyletoun
- Člen | 667
Ahoj, dost užitečná přednáška.
Akorát na jednom příkladu(Agregační funkce #2, slide24)
jsem zpozoroval toto a zdá se mi to podezřelé: Proč v druhém dotazu je
využit sloupec translator_id?
--1
SELECT `id`, `name` FROM `author`
--2
SELECT COUNT(*), `book`.`translator_id`
FROM `book`
WHERE (`book`.`translator_id` IN (11, 12))
GROUP BY `book`.`translator_id`
$connection = $container->nette->database->default;
foreach ($connection->table('author') as $author) {
echo $author->name;
echo $author->related('book')->count('*');
}
A potom ještě jeden dotaz,(Cíl:ať se vypisují tagy a k nim autoři,
kteří napsali knihu s daným tagem. ) Momentálně se vypisují tagy a k nim
autoři knih s daným tagem(někteří autoři tedy i víckrát).
Akorát nemohu přijít na zápis, abych dostal autory neduplicitní.
foreach ($connection->table('tag') as $tag) {
echo $tag->name;
echo ":::<br>";
// ? jak vybrat pouze unikátní autory
foreach ($tag->related('book_tag') as $value) {
// autory, kteri napsali vic knih se stejnym tagem, vypise vickrat, např.:
//PHP:::
//Jakub Vrana
//Jakub Vrana
//David Grudl
//David Grudl
echo $value->book->author->name;
echo "<br>";
}
echo "<br>";
}
V podstatě mě napadly tyto možnosti:
- pokračovat foreach ($tag…)
- od konce ($connection->table(author->…) – což generuje O(N) dotazů
- využít getReferencedTable?
Editoval bojovyletoun (2. 5. 2012 16:06)
- Caine
- Člen | 216
Člověk se pořád učí:) Např. to s tou dvojtečkou.. V dokumentaci o tom neni ani zmínka a kvůli tomu jsem doteď takový dotazy řešil nějak ve smyslu drbat se levou rukou za pravym uchem:/
Jinak docela bych do Selection bral metody typu fetchColumn, i možná to fetchAll, nechcete to tam přidat?:)
- duke
- Člen | 650
bojovyletoun napsal(a):
fetchAll jsem zkusil(před 4 měsíci)) přidat (byl bych dal odkaz rovnou na větev , ale druhý commit u ActiveRow je dost experimentální)
A není jednodušší prostě použít funkci iterator_to_array?
A nechápu, proč by fetchAll
měla narozdíl od
fetchPairs
automaticky převádět řádky na pole.
- marek.dusek
- Člen | 99
paranoiq napsal(a):
@marek.dusek: otázky typu „co když mi někdo mezitím…“ řeší už pouhé uzavření operací do transakce. na MySQL je defaultně izolace REPEATABLE READ a ta právě tohle zajišťuje (ale dost zamyká)
to neni uplne pravda – REPEATABLE zaruci, ze „ti nikdo nic nesmaze ani nezmeni“, ale nezaruci, ze nevyberes neco navic (commitovane inserty se do novych selectu zaradi) – tj. dotaz na COUNT muze vratit 5 zaznamu a vyber LIMIT 0, 100 jich vratit klidne 6 (nerikam, ze to „vadi“, ale ze to neni totez)
jinak diky za vysvetleni, ze v mysql je to default – to je docela zasadni informace a netusil jsem, ze jde takto „proti proudu“ (oracle / mssql / postgre, vsude je default read commited) – asi se predpoklada, ze pokud na mysql pobezi neco vytizeneho, bude tam nekdo kompetentni, koho napadne toto zmenit :-)
Editoval marek.dusek (2. 5. 2012 22:47)
- hrach
- Člen | 1838
Ad zamykani: ano, vsechno co zaznelo mi zni velmi zajimave a honosne, nejsem ale takovej profik :) takze vam panove verim a diky za objasneni a doplneni. Jen bych dodal z praxe – cim mene zamykani, tim lepe :D (to bylo ale moudro, co? :D)
@bojovyletoun dotaz #1: diky moc za pripomenuti tohoto, uz jsem to nekde videl a zapomnel jsem na to. bug i potrebnou feature jsem opravil a nyni se discoveredReflection chova tak, jak se ceka. Opravu jsem pushnul k sobe, muzes prosim otestovat https://github.com/…-refactoring
@bojovyletoun dotaz #2: covece, dumam nad tim tady a nevim. Nechce napsat nekdo klasicky sql dotaz, abych to pak jen prepsal do ndb?
- bojovyletoun
- Člen | 667
fetch
- u fetchPairs bych nechal v případě prázdného 1.arg (key) defaultně prim.klíč (a nebo číslovat od nuly-další možnost)
- na to převádění na pole ($r->toArray()) byl asi návyk z dibi. Velmi
se mi to hodí př experimentech.
Jinak je pravda, že iterator_to_array() to řeší, ale fluent rozhraní je přeci lepší.
Dotaz tag-author
Číst celé, je tu 1 ihned funkční řešení, 1 rádoby bug a druhé řešení, které by bylo nebýt bugu
SQL dotaz jsem si napsal: (duplicitní chování jako výše se vyvolá smazáním kouzelného slůvka distinct)
-- 1. metoda
select distinct tag.*, author.*
from tag
join book_tag on tag.id=book_tag.tag_id
join book on book_tag.book_id=book.id
join author on book.author_id=author.id
order by tag.id
-- 2. metoda, jako bonus muzeme zjistit agreg.
select tag.*, author.*,count(*) as kolikKnihDanehoAutoraDanehoTagu
from tag
join book_tag on tag.id=book_tag.tag_id
join book on book_tag.book_id=book.id
join author on book.author_id=author.id
group by tag.id,author.id
order by tag.id
Mimochodem zápis v ND vypadá :
//1a bez distinct vypisuje duplicity
foreach ($connection->table('tag') as $tag) {
echo $tag->name;
echo ":::<br>";
foreach ($tag->related('book_tag')->select(' (book.author.name) AS q') as $value) {
echo $value->q;
echo "<br>";
}
echo "<br>";
}
//1b zkusim distinct, ale shori to na tom, ze keyword distinct musi byt prvni
foreach ($connection->table('tag') as $tag) {
echo $tag->name;
echo ":::<br>";
foreach ($tag->related('book_tag')->select(' DISTINCT (book.author.name) AS q') as $value) {
echo $value->q;
echo "<br>";
}
echo "<br>";
}
//SELECT `book_tag`.`tag_id`, distinct(`author`.`name`) AS `q`
//FROM `book_tag`
//LEFT JOIN `book` ON `book_tag`.`book_id` = `book`.`id`
//LEFT JOIN `author` ON `book`.`author_id` = `author`.`id`
//WHERE (`book_tag`.`tag_id` IN (21, 22, 23, 26))
//........................1064 You have error in your
//SELECT distinct(`author`.`name`) AS `q`,`book_tag`.`tag_id`
//zbytek stejny
//funguje!
//podarilo se mi to vyresit i 2. metodou group
foreach ($connection->table('tag') as $tag) {
echo $tag->name;
echo ":::<br>";
foreach ($tag->related('book_tag')
->group('tag.id,book.author.name')
->select('book.author.name AS q, count(*) AS c')
as $value) {
echo $value->q;
echo $value->c;
echo "<br>";
}
echo "<br>";
}
ad 1-distinct)Je možné upravit Selection, aby dával distinct na první místo?
Pozn- obě metody (distinct a group) jsou totožné dle explainu.
Stojí tohle za to dát do kuchařky(2.metodu)?
Editoval bojovyletoun (3. 5. 2012 0:48)
- paranoiq
- Člen | 392
@marek.dusek: izolace REPEATABLE READ zamyká i mezery v indexech, takže do indexovaného dotazu nikdo jiný během transakce nic přidat nemůže (viz třeba: http://www.mysqlperformanceblog.com/…s-gap-locks/). u neindexovaného nejspíš ano. izolaci jsem po problémech se zámky musel snížit a kde je to třeba, tam používám SELECT FOR UPDATE
- hrach
- Člen | 1838
Dotaz tag-author
Jediné správné řešení™ je toto: (přesně odpovídá konvencím z přenášky):
$tagsAuthors = array();
foreach ($connection->table('tag') as $tag) {
$book_tags = $tag->related('book_tag')
->group('tag.id, book.author.id')
->select('book.author_id')
->order('book.author.name');
foreach ($book_tags as $book_tag) {
$tagsAuthors[$tag->name][]
= $book_tag->ref('author', 'author_id')->name;
}
}
O toto jsem se včera snažil, ale už jsem na to neměl síly. Díky moc
@bojovyletoun za navedení :)
Generuje spravně jen 3 optimální dotazy, při joinech
zahovává princip šahání do indexu a správně umožňuje dekompozici
do entit.
Editoval hrach (3. 5. 2012 10:29)
- duke
- Člen | 650
duke napsal:
A není jednodušší prostě použít funkci iterator_to_array?bojovyletoun napsal:
Jinak je pravda, že iterator_to_array() to řeší, ale fluent rozhraní je přeci lepší.
Má otázka i v tomto případě stále platí a vztahuje se k Tvé implementaci fetchAll. Tj. není v rámci implementace fetchAll lepší interně použít funkci iterator_to_array? Tj.:
public function fetchAll()
{
return iterator_to_array($this);
}
Osobně ale příliš velký rozdíl ve „fluent“ faktoru mezi následujícím nevidím. Srovnej:
$authors = iterator_to_array($db->table('authors'));
vs
$authors = $db->table('authors')->fetchAll();
Obojí přece vrací pole, takže žádný další fluent call se již konat
nemůže.
Jediný rozdíl, který zde je, je čistě estetický.
- bojovyletoun
- Člen | 667
Akorát můžeš vysvětlit konstrukci
$book_tag->ref('author', 'author_id');
– ta vazba z book_tag
na autora je na mě moc magická
.Odebral jsem order.jo a ještě jedna věc- záměna podtržítek za tečky
v group způsobuje přidání/odebrání joinů.(v druhém dotazu) (Otázka
je, zda to může způsobit neplechu, např, když je nějaká kolekce
prázdná). Informativní počty joinů(s tečkami oboje řešení 3 joiny, s
_ 2joiny/1join)
Pro jistotu ještě zrekapituluji oba kódy:
//bojovyletoun
foreach ($connection->table('tag') as $tag) {
echo $tag->name, ": ";
$book_tags = $tag->related('book_tag')
->group('tag_id, book.author_id')
->select('book.author.name AS name');
foreach ($book_tags as $groupAuthor) {
echo $groupAuthor->name, " ";
}
echo "<br>";
}
echo "<br><br>";
//jan
foreach ($connection->table('tag') as $tag) {
echo $tag->name, ": ";
$book_tags = $tag->related('book_tag')
->group('tag_id, book.author_id')
->select('book.author_id') ;
foreach ($book_tags as $book_tag) {
echo $book_tag->ref('author', 'author_id')->name, ", ";
}
echo "<br>";
}
Editoval bojovyletoun (4. 5. 2012 13:13)
- hrach
- Člen | 1838
- podtrzitko je tam jen kvuli tomu, ze ten join neni treba, takze pro group me staci uz sloupec v tabulce book
- neplechu by to delat nemelo. Kdyz tage nema knihy, nebude mit ani autory…
- konstrukce ref tam je kvuli DiscoveredReflection. Jde o to, ze kdybych
pouzil
$book_tag->author->name
tak by Conventional reflection spravne poznal, ze ma tahat data z author a logicky by si odvodil ze to ma braz ze sloupce author_id. DiscoveredReflection ale zadnou vazbu mezi tabulkou book_tag a author samozrejme nezna, tak mu musim rict, ze ma sahat do tabulky author a pouzivat k tomu sloupec author_id, ktery jsme si doselectovali. - druhy pristup je vyrazne lepsi, protoze pokud budu chtit vypsat neco vic nez jmeno, nemusim nic menit. V tvem pripade bych musel rucne pridavat do voladni selectu dalsi aliasove sloupce…
- bojovyletoun
- Člen | 667
tak jsem nějak nepřečetl, že jsi ten bug taky fixnul a já se to rozhodl prozkoumat, takže jsem taky transponoval záznamy v structure->hasMany (aby klíče pole byly sloupce a hodnoty tabulky) a pak ještě řešil prioritya v tom se rozhodl podívat no forum a zjistil, že si to vyřešil…
Jo ještě si nevím rady s poslední věcí: stejný příklad: u tagu vypsat ke každému autorovi počet knih pomocí API
foreach ($connection->table('tag') as $tag) {
echo "{$tag->name},({$tag->related('book_tag')->count('*')} #books, ";
$book_tags = $tag->related('book_tag')
->group('tag_id, book.author_id')
->select('book.author_id, count(*) AS countInSql'); // count je tu jen, aby byl vidět cíl snažení
echo "{$book_tags->count()} #authors), ";
foreach ($book_tags as $book_tag) {
$author = $book_tag->ref('author', 'author_id');
//echo "{$author->name}({$book_tag->countInSql}), ";
echo "{$author->name}({?}), "; // jak vypsat počet knih daného autora a tagu
}
echo "<br>\n";
}
PHP(5 #books, 2 #authors), Jakub Vrana(3), David Grudl(2),
MySQL(2 #books, 2 #authors), Jakub Vrana(1), David Grudl(1),
JavaScript(1 #books, 1 #authors), Jakub Vrana(1),
VVV(0 #books, 0 #authors),
Editoval bojovyletoun (6. 5. 2012 18:15)