Vytažení náhodného záznamu z DB

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

Ahoj,

lze doplněním něčeho do

$this->db->select('*');

vytáhnout z DB jeden náhodný záznam, namísto všech?

Děkuji.

Editoval weckho (29. 8. 2009 18:19)

Panda
Člen | 569
+
0
-

Toto by mohlo být relativně rychlé (subquery používá index):

$this->db->select('*')->from('table')->where('%n >= RAND() * (SELECT MAX(%n) FROM %n)', 'id', 'id', 'table')->limit(1);

Testoval jsem to na MySQL a zdá se, že to funguje bez problémů. Po drobných změnách by to mělo fungovat i v jiných databázích.

Editoval Panda (29. 8. 2009 19:07)

weckho
Člen | 94
+
0
-

Díky moc, dotaz jako takový funguje, bohužel mi vytahuje z DB vždy poslední záznam. Kde by mohla být chyba?

Díky

Panda napsal(a):

Toto by mohlo být relativně rychlé (subquery používá index):

$this->db->select('*')->from('table')->where('%n >= (SELECT MAX(%n) FROM %n)', 'id', 'id', 'table')->limit(1);

Testoval jsem to na MySQL a zdá se, že to funguje bez problémů. Po drobných změnách by to mělo fungovat i v jiných databázích.

Panda
Člen | 569
+
0
-

weckho napsal(a):

Díky moc, dotaz jako takový funguje, bohužel mi vytahuje z DB vždy poslední záznam. Kde by mohla být chyba?

Mezi mojí klávesnicí a židlí, zapomněl jsem tam napsat RAND(). :)

Správně to vypadá takto:

$this->db->select('*')->from('table')->where('%n >= RAND() * (SELECT MAX(%n) FROM %n)', 'id', 'id', 'table')->limit(1);

Opravím to i v původním příspěvku, aby to někoho nemátlo.

Honza Marek
Člen | 1664
+
0
-

Což takhle:

$this->db->select("*")->from("table")->orderBy("rand()")->limit(1);

Jinak bych si troufnul říct, že na fóru nette je to lehký off-topic.

pmg
Člen | 372
+
0
-

To je funkční způsob, který ale MySQL neumí optimalizovat, takže se RAND() volá (n * log n)-krát.

Lze to optimalizovat jako SELECT *, RAND() AS foo FROM table ORDER BY foo.

Složitější způsob je uložit si ke každému řádku náhodnou hodnotu a podle ní pak vybírat, tak to dělá Wikipedie. Je to rychlé, ale když se některé hodnoty dostanou moc blízko, některé záznamy to nevytáhne. Leda hodnoty po čase přegenerovat.

Panda
Člen | 569
+
0
-

pmg napsal(a):

To je funkční způsob, který ale MySQL neumí optimalizovat, takže se RAND() volá (n * log n)-krát.

Lze to optimalizovat jako SELECT *, RAND() AS foo FROM table ORDER BY foo.

Dovolil jsem si provést benchmarky a zdá se, že ani tento způsob MySQL moc optimalizovat neumí. Měl jsem tabulku jen o 7 řádcích, ale 1 000 000 průchodů mému dotazu zabralo kolem 3.5s, Tvé řešení trvalo při stejném počtu něco málo přes 20s. Podobně na tom bylo i původní řešení od Honzy Marka.

U tabulky locales_source z jedné testovací instalace Drupalu (3441 řádků) byl rozdíl znát ještě mnohem víc. Při 1 000 průchodech zabrala obě řešení s ORDER BY kolem 4s, mé řešení kolem 60ms. Problém je v tom, že MySQL musí nejdřív všem řádkům vygenerovat náhodnou hodnotu a pak to podle ní setřídit. K tomu musí použít dočasnou tabulku a na třídění nemůže použít indexy.

Testoval jsem na MySQL 5.0.67, Win32 (XP SP3), obě tabulky MyISAM.

Jinak souhlasím s Honzou, na fóru Nette je to trochu off-topic.

pmg
Člen | 372
+
0
-

Tvoje řešení je bezpochyby rychlejší, jen je trochu složitější a hlavně se jeho náhodnost bude snižovat s rozptylováním hodnot id.

A díky za ten benchmark, vycházel jsem z předpokladu, který jsem před pár lety někde četl, ale nikdy neotestoval. Možná se to s pátou verzí MySQL změnilo, myslím moje vs Honzovo řešení.