Ciekawy tekst i proponowane rozwiązania. Jest jednak jedno “ale”. Mianowicie tych metod, jako że korzystają z MAX(id), MIN(id) etc., nie można wykorzystać w przypadku, kiedy identyfikatory kolejnych rekordów nie utrzymują ciągłości. Tzn. jeśli np. jakiś rekord zostanie wykasowany fizycznie z bazy, to jest szansa, że losowanie z zakresu pomiędzy MIN a MAX może trafić w ten właśnie rekord.
Możliwe, że to m.in. dlatego funkcja: SELECT `id`,`key` FROM `keys`ORDER BY RAND() LIMIT 1; potrafi zająć tyle czasu – ale przynajmniej w jej przypadku można być pewnym zwracanego wyniku.
@Rumcajs
Tak, masz rację, właśnie dlatego pisałem na końcu części “Satysfakcjonujące rozwiązanie” o tabeli tymczasowej, która przechowywałaby klucze do istniejących rekordów: ID | KEY_ID , a całe losowanie id wykonywane byłoby na tej tabeli tymczasowej.
@Rumcjajs: wg mojego czytania ze zrozumieniem – mylisz się 😉
Dlatego “ktoś” napisał warunek: WHERE `id` >= $random aby uniknąć wpadnięcia pod MIN/MAX gdy rekord już usunięty…
Oczywiście brzydkie rozwiązanie, ale działa tak jak autor napisał
@matipl: tu chodzi o sytuacje kiedy nie bedzie istnial rekord pomiędzy min id a max id.
Jeśli sa id: 2,3,4,6,7 to min jest 2 max 7 a może się wylosować 5.
O tym napisał @Rumcajs
Warunek WHERE jest `id`>=, nie powoduje losowego wybrania elementu z bazy, gdyz faworyzuje pewne elementy
przyklad
mamy id: 1,2,3,7,8,9
najczesciej losowanym elementem bedzie 7 i to az 4 razy czeesciej niz inne.
Fajne rozwiązanie na które kiedyś się natknąłem i często stosuję to zliczenie wszystkich wierszy, wylosowanie numeru strony, a następnie pobranie losowej strony. Działa szybko i jest proste.
Szczerze, dopiero zaczynam swoją przygodę z Mysql ale wpadłem na pewien pomysł, który powinna zweryfikować osoba posiadająca większą wiedzę.
Czy wydajnym nie było by wykonanie zapytania num() o ilość rekordów, następnie za pomocą mt_randa określenie jakiegoś limitu w zapytaniu, a następnie wylosowanie wśród tych, dajmy na to 10 losowych rekordów?
Świetny tekst – o bardzo popularnym problemie – i jak widać, często źle rozwiązywanym. Dzięki.
Niestety kombinacja “ORDER BY RAND()” jest mordercza, ale często powtarzana i w wielu małych aplikacjach się zdarza…
Wiesz, nieraz lepiej zrobić 2 proste zapytania resztę zrzucając na język programowania, niż jedno przepakowane.
Ciekawy tekst i proponowane rozwiązania. Jest jednak jedno “ale”. Mianowicie tych metod, jako że korzystają z MAX(id), MIN(id) etc., nie można wykorzystać w przypadku, kiedy identyfikatory kolejnych rekordów nie utrzymują ciągłości. Tzn. jeśli np. jakiś rekord zostanie wykasowany fizycznie z bazy, to jest szansa, że losowanie z zakresu pomiędzy MIN a MAX może trafić w ten właśnie rekord.
Możliwe, że to m.in. dlatego funkcja:
SELECT `id`,`key` FROM `keys`ORDER BY RAND() LIMIT 1;
potrafi zająć tyle czasu – ale przynajmniej w jej przypadku można być pewnym zwracanego wyniku.@Rumcajs
Tak, masz rację, właśnie dlatego pisałem na końcu części “Satysfakcjonujące rozwiązanie” o tabeli tymczasowej, która przechowywałaby klucze do istniejących rekordów:
ID | KEY_ID
, a całe losowanie id wykonywane byłoby na tej tabeli tymczasowej.@Rumcjajs: wg mojego czytania ze zrozumieniem – mylisz się 😉
Dlatego “ktoś” napisał warunek: WHERE `id` >= $random aby uniknąć wpadnięcia pod MIN/MAX gdy rekord już usunięty…
Oczywiście brzydkie rozwiązanie, ale działa tak jak autor napisał
@matipl: tu chodzi o sytuacje kiedy nie bedzie istnial rekord pomiędzy min id a max id.
Jeśli sa id: 2,3,4,6,7 to min jest 2 max 7 a może się wylosować 5.
O tym napisał @Rumcajs
@quentino: Dlatego w warunku WHERE jest `id`>=, w przypadku gdy nie ma wiersza o id 5 zostanie zwrócony następny w kolejności (istniejący)
Warunek WHERE jest `id`>=, nie powoduje losowego wybrania elementu z bazy, gdyz faworyzuje pewne elementy
przyklad
mamy id: 1,2,3,7,8,9
najczesciej losowanym elementem bedzie 7 i to az 4 razy czeesciej niz inne.
Fajne rozwiązanie na które kiedyś się natknąłem i często stosuję to zliczenie wszystkich wierszy, wylosowanie numeru strony, a następnie pobranie losowej strony. Działa szybko i jest proste.
SET @r := (SELECT ROUND(RAND() * (SELECT COUNT(*) FROM tabelka)));
SET @sql := CONCAT(‘SELECT * FROM tabelka LIMIT 1 OFFSET ‘, @r);
PREPARE stmt1 FROM @sql;
EXECUTE stmt1;
powinno dzialac i nie ma problemu z ciagloscia rekordow
Skąd takie czasy? “1 row in set (0.61 sec)”
Kiedy u mnie przy 20tys rekordów i limit 10 wychodzi: “10 wszystkich, Wykonanie zapytania trwało 0.0222 sekund(y))”
Przy 100tys tak drastycznie wzrosło? Wątpię.
Szczerze, dopiero zaczynam swoją przygodę z Mysql ale wpadłem na pewien pomysł, który powinna zweryfikować osoba posiadająca większą wiedzę.
Czy wydajnym nie było by wykonanie zapytania num() o ilość rekordów, następnie za pomocą mt_randa określenie jakiegoś limitu w zapytaniu, a następnie wylosowanie wśród tych, dajmy na to 10 losowych rekordów?