Přednáška Nette\Database na poslední sobotě

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
hrach
Člen | 1834
+
0
-

„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
+
0
-

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
+
0
-

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:

  1. pokračovat foreach ($tag…)
  2. od konce ($connection->table(author->…) – což generuje O(N) dotazů
  3. využít getReferencedTable?

Editoval bojovyletoun (2. 5. 2012 16:06)

Caine
Člen | 216
+
0
-

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

bojovyletoun
Člen | 667
+
0
-

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

Editoval bojovyletoun (2. 5. 2012 20:02)

paranoiq
Člen | 392
+
0
-

@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á)

duke
Člen | 650
+
0
-

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
+
0
-

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 | 1834
+
0
-

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
+
0
-

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
+
0
-

@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 | 1834
+
0
-

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
+
0
-

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
+
0
-

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 | 1834
+
0
-
  • 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
+
0
-

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)