Update databáze náhodný řádek

viyrag
Člen | 8
+
0
-

Dobrý den,
Potřeboval bych poradit jak updatovat náhodný řádek podle statusu = 0 a userId = NULL.

Respektive mám několik záznamů v tabulce a potřebuji updatovat na status = 1 a userId = nick, pouze jeden náhodný řádek, který se nebude opakovat.

Děkuji moc.

Polki
Člen | 553
+
+2
-
$this->db->table('table_name')
    ->where('status', 0)
    ->where('userId', null)
    ->order('RAND()')
    ->limit(1)
    ->update([
        'status' => 1,
        'userId' => $userId,
    ]);

Jen doporučuju dodržovat v rámci databáze jmenné konvence a pojmenovat sloupec ‚user_id‘

Editoval Polki (30. 8. 2021 17:13)

David Grudl
Nette Core | 8227
+
+3
-

Což je zároveň nejméně efektivní řešení, ale u malých tabulek to nevadí.

Polki
Člen | 553
+
0
-

@DavidGrudl
Prosím napiš efektivní. Já totiž myslel, že takto je to správně. Udělá to 1 jediný dotaz, vybere na základě hodnot, které potřebujeme a taktéž to upraví jen ty hodnoty, které potřebujeme.

Jediný problém vidím v řazení a zahození zbytku, když chci jen 1 prvek.

Vzhledem k tomu, že ale snad ve všech článcích se vybírá náhodný řádek pomocí rand a učí to tak i na školách mi přijde, že to je v pohodě.

Nebo myslíš, že by bylo lepší dělat 2 dotazy, jeden na všechny řádky vyhovující podmínkám, potom pomocí random funkce vygenerovat index některého z řádků a nad tím zavolat update? To mi zase při větších tabulkách sežere ramku…

Jediné co mě napadá je při insertu nového prvku vygenerovat náhodné číslo a podle toho to pak řadit.. Aby se random negeneroval.

EDIT 1:
Ještě mě napadlo něco jako:

$query = $this->db->table('tableName')
    ->where('status', 0)
    ->where('user_id', null);

$offset = $query
    ->count('id');


$aaa = $query
    ->limit(1, rand(0, $offset))
    ->fetch();

$aaa->update([
    'status' => 1,
    'user_id' => 2,
]);

Ale rozdíl mezi prvním příspěvkem (13155ms) a těmito dotazy (11524ms) není tak markantní, aby se tím člověk moc zaobíral si myslím.
(testován na 30 milionech záznamů a tabulkou se 30 sloupci – číslo 30 je super :D)

Editoval Polki (30. 8. 2021 21:56)

viyrag
Člen | 8
+
+1
-

@DavidGrudl
@Polki

Děkuji vám oběma moc!
Byl vyřešen můj problém :)

Kamil Valenta
Člen | 820
+
+1
-

David Grudl napsal(a):

Což je zároveň nejméně efektivní řešení, ale u malých tabulek to nevadí.

Na tohle je @Milo strašně háklivej :)
Ale opravdu by stálo za to to rozvést, protože pokud jsou na status a user_id indexy, tak marně přemýšlím, jak to udělat efektivněji.

Spíš bych řekl, že (ne)efektivita bude závislá na počtu status=0, user_id=null. Pokud takových vět bude velké množství, asi bych bokem dělal rand() z jejich id a do sql už jen na konkrétní větu.
Pokud se vybírá z několika málo záznamů, tak by mně efektivnější řešení také zajímalo.

Souvislost s velikostí tabulky tam ale nevidím.

David Grudl
Nette Core | 8227
+
+2
-

Je možné, že nějaké DB už optimalizují ORDER BY RAND() LIMIT 1, a nedělají to, že nejprve setřídí celou tabulku (!) a pak vyberou první řádek, ale nevím, není to můj obor, takže do toho nechci víc zabrušovat :)

Kamil Valenta
Člen | 820
+
+1
-

ORDER BY RAND() LIMIT 1 netřídí celou tabulku, ale jen resultset ovlivněný WHERE.
A to je to zásadní, co může query pohřbít, nebo učinit legitimní.

Pokud to WHERE např. pomocí indexu stáhne na 5 vět, udělat z nich RAND bude chvilka, ačkoliv tam bude stále filesort.

David Grudl
Nette Core | 8227
+
+2
-

Jasně, myslíme to stejné.

Polki
Člen | 553
+
0
-

@KamilValenta
Pravdu máš. Moje testy, jak jsem psal výše běžely ještě nad MariaDB 10.4.10 a schválně jsem všech 30M záznamů měl nastavených, aby splňovaly podmínku where.

A 1.5 s rozdíl mi přišel, že to musí mít optimalizované a netřídí tedy celý výsledek wheru, ale setřídí jen tolik záznamů, kolik je napsáno v limit.

Nepřijde mi totiž, že celé řazení podle náhody by pro 30M prvků zabralo jen 1.5s

Kamil Valenta
Člen | 820
+
+1
-

Polki napsal(a):

A 1.5 s rozdíl mi přišel, že to musí mít optimalizované a netřídí tedy celý výsledek wheru, ale setřídí jen tolik záznamů, kolik je napsáno v limit.

No to už asi ne. Musí setřídit celý where a pak z něj udělat limit. Jinak by to ani nedávalo smysl.

Polki
Člen | 553
+
0
-

Mě to sice smysl dává, ale do core MariaDB nevidím.

Každopádně vygenerovat 30M náhodných hodnot a seřadit podle nich 30M záznamů mi přijde složitější, než na 1,5s

EDIT 1:
Stroj, na kterém to je pouštěno:
Windows 10
16GB RAM DDR4
Intel Core i7–9750H, 2.60GHz

Editoval Polki (31. 8. 2021 21:10)

Kamil Valenta
Člen | 820
+
0
-

Polki napsal(a):

Mě to sice smysl dává, ale do core MariaDB nevidím.

Tak si představ, že máš tabulku se zbožím a chceš nejlevnější produkt.

->order('price')->limit(1)

Napřed musíš setřídit, pak limitem ukrojíš, kolik je potřeba.
Pokud bys třídil jen tolik, kolik je v limitu, dostal bys vždy jen první produkt, prováděcí plán SQL by neměl šanci zjistit, zda někde dál není záznam s nižší cenou.

RAND() funguje podobně, jen se vytvoří temp sloupec, do kterého se pro každý record uloží náhodná hodnota – a podle toho se pak setřídí…

EDIT:
Ale klidně si dej před dotaz EXPLAIN a uvidíš, na kolik řádků to sáhlo…

Editoval Kamil Valenta (31. 8. 2021 21:24)

Polki
Člen | 553
+
+1
-

No myslel jsem, že pokud mám nad sloupcem index, tak má databáze uložený daný sloupec ještě v binárním vyhledávacím stromě, což zajistí vyhledání daného prvku logaritmickou složitostí, díky čemuž, když budu chtít nejlevnějších N prvků, tak vlastně order nad sloupcem ‚price‘ jen určí směr průchodu stromem a žádný sort se dělat nemusí, protože z podstaty stromu je už seřazeno, jen se díky limitu neprojde celý strom, ale ve tvém dotazu se sebere jen první nejlevější prvek..

Stejně by to mohlo fičet i s randem, který by mohl fungovat třeba tak, že by vzal strom s ID a procházel by jej náhodně (šel by buď vlevo, nebo vpravo), dokud by se nedostal k listu, který by vložil do nového stromu a v momentě, kdy by bylo ve stromě tolik prvků, kolik je uvedeno v limit, tak by mohl s random vyhledáváním skončit. (nebo aby pořád nevyhledával, tak by mohl generovat náhodné indexy do pole s ID třeba, čím by u vyhledávání shodil složitost z O(log(n)) na O(k)…)

To, že to tak není je pro mě docela rozčarování :D No co. O databázi jako takovou jsem se nikdy dopodrobna nezajímal. Myslel jsem, že to mají schválně optimalizované, aby to ten, kdo ji používá nemusel řešit. Co už :D

PS: stejně tak jsem myslel, že count prostě vrátí konstantní číslo, kolik prvků má daný podstrom vrácený clausulí where. Taky to pro mě byl docela šok, že si toto db neuchovává a pokaždé počítá, kolik je v daném podstromě prvků a pro 30M záznamů trvá spočítat všechny podle ID 5.5s

EDIT 1:
Musí to mít nějaké optimalizace, nebo DB neumí počítat, protože EXPLAIN jak píšeš mi vyhodil, že sahá na 11336942 řádků a v db je jich 33240016

Editoval Polki (31. 8. 2021 22:00)

David Grudl
Nette Core | 8227
+
+1
-

Databáze nejprve udělá WHERE, potom ORDER BY a nakonec LIMIT. Takže ORDER BY řadí všechny vybrané řádky. A nelze k tomu využít index, protože se neřadí podle sloupce, ale podle funkce RAND(), ke které žádný index není.

Polki
Člen | 553
+
0
-

David Grudl napsal(a):

Databáze nejprve udělá WHERE, potom ORDER BY a nakonec LIMIT. Takže ORDER BY řadí všechny vybrané řádky. A nelze k tomu využít index, protože se neřadí podle sloupce, ale podle funkce RAND(), ke které žádný index není.

Jasně, tomu rozumím. Jen mi to přijde docela škoda.