Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > MySQL > Выборка случайной записи из большой таблицы


Автор: dm9 2.11.2006, 12:09
Здравствуйте.

У меня стоит задача (быстрой) выборки одной случайной строки из таблицы. MySQL 4.1/5.

Я вижу несколько вариантов.
1. Метод чисто СУБД-шный.
Код
SELECT ... ORDER BY RAND() LIMIT 1

Почему это не работает на большой таблице — понятно.

2. Использование скриптового кода.
$cnt = (SELECT COUNT(*) FROM ...)
$rnd = rand(0, $cnt-1)
Затем SELECT ... LIMIT $rnd, 1
Ну или то же самое запихать в хранимую процедуру в MySQL 5.

3. Оптимизированный второй вариант.
Периодически (кроном или т. п.) выполнем выборку $cnt = (SELECT COUNT(*) FROM ...)
Затем в своём скрипте считываем этот $cnt и используем далее как во втором варианте.
Только нужна проверка — если число выбранных таким образом строк равна нулю, значит, строки удалились — тогда делаем ещё раз COUNT(*) и далее опять как во втором варианте.
Если строки удаляются редко, этот вариант в большинстве случаев экономит нам один запрос, хотя иногда приводит к трём запросам вместо 1 или 2.

Есть ли ещё какие-то варианты для реализации подобных выборок?

Автор: skyboy 2.11.2006, 12:31
dm9, если в этой таблице есть автоинкрементый ключ, то можно сгенерировать rand(max(id)-min(id))+min(id) и выбрать первое значение, большее этого... ORDER BY id должен ведь сработать...
что-то вроде
SELECT ... FROM <table> WHERE id> rand()*(max(id) - min(id))+min(id) ORDER BY id limit 1
Впрочем, order делать не надо, то, что вернет тебе в случайном порядке, даже лучше... впрочем, надо бі изучить распределение при помощи опытов...
т.е. результат:
SELECT ... FROM ... WHERE id> rand()*(max(id)-min(id)) + min(id)

Автор: dm9 2.11.2006, 18:04
Интересное решение. Спасибо.

А ещё у кого-нибудь варианты есть под MySQL?

Автор: sergejzr 18.4.2007, 16:20
Меня тоже вопросик интересует...

Автор: Gold Dragon 18.4.2007, 20:59
а можно ли в MySQL вытащить из таблицы определённую записть по номеру, ну типа как в ассоциативном массиве вытащить запись по порядку? 
Можно было бы сгенерить просто случайное число и вытащить запись с этим номером ...

Автор: sergejzr 18.4.2007, 22:19
Цитата(Gold Dragon @  18.4.2007,  19:59 Найти цитируемый пост)
Можно было бы сгенерить просто случайное число и вытащить запись с этим номером ...

Можно что-то вроде этого, но если эта запись не будет соответствовать другим условиям выборки?

Автор: Gold Dragon 19.4.2007, 06:38
согласен. 

кстати
Цитата(dm9 @  2.11.2006,  12:09 Найти цитируемый пост)
1. Метод чисто СУБД-шный.код
SQL1:SELECT ... ORDER BY RAND() LIMIT 1
Почему это не работает на большой таблице — понятно.
И на сколько этоплохо

Автор: dm9 19.4.2007, 12:41
Помнится, мы экспериментировали, и работало это очень долго на больших таблицах.
Видимо, MySQL считает этот RAND() для каждой записи таблицы, а потом filesort... Теоретически "ORDER BY RAND()" можно было бы попробовать соптимизировать внутри СУБД, но, видно, этого по каким-то причинам не сделали.

Кстати, по-моему, даже в официальном мануале было что-то сказано по поводу того, что для больших таблиц ORDER BY RAND() делать не рекомендуется. Это можно найти в описании ф-ции RAND(), если интересно.

Автор: kronos_vano 9.3.2009, 10:27
Код

select * from <table> where id>=(select FLOOR(RAND() * COUNT(*)) from <table>) limit 1;

Мой вариант smile

Автор: Бонифаций 9.3.2009, 12:13
Цитата(kronos_vano @ 9.3.2009,  10:27)
Код

select * from <table> where id>=(select FLOOR(RAND() * COUNT(*)) from <table>) limit 1;

Мой вариант smile

пойдем заново по кругу? 

А если id в таблице с пропусками? скажем id с 1000 до 2000 отсутствует? тогда по Вашей схеме id 2000 будет выбираться чаще других.. 



Автор: sergejzr 9.3.2009, 16:27
Кстати вроде не было метода:

1) cnt="select count(*) from t where bla bla"

Этот запрос закешируется, а если условия нет, то count(primary_key) вообще всегда готовый лежит. т.е высчитывать не надо каждый раз.

Ну а дальше высчитываем случайное число r=rand(cnt):

И наконец:

"select * from <table> where bla bla limit r,1;"

Автор: skyboy 9.3.2009, 16:47
sergejzr, smile
первый пост темы, второе предложение dm9 smile
но, пожалуй, самый правильный способ, потому не лишне будет ещё раз про него упомянуть.

Автор: sergejzr 9.3.2009, 18:01
Да, что-то я бояню smile

Автор: kronos_vano 9.3.2009, 21:16
Бонифаций, есть предложения как сделать лучше для таблицы с миллионами записей?

Автор: Бонифаций 10.3.2009, 03:59
Цитата(kronos_vano @ 9.3.2009,  21:16)
Бонифаций, есть предложения как сделать лучше для таблицы с миллионами записей?

правильный путь озвучен здесь http://jan.kneschke.de/projects/mysql/order-by-rand/

Автор: sergejzr 10.3.2009, 11:14
В принципе триггером содержать таблицу идентификаторов не такая уж плохая идея. 

Автор: sergejzr 14.8.2009, 12:23
ещё один способ:

Предположим надо выбрать из таблицы "cooccurrences" 2 случайных записей.
$cnt нам известен,. Скриптом генерим два случайных числа rand()%$cnt. предположим получились 15,20 

Код

SELECT * FROM (SELECT @row:=@row+1 as rownum, cooccurrences.* FROM (SELECT @row=0)r,cooccurrences) ranked WHERE rownum IN(15,20) 


Получается, что запрос нумерует все записи таблицы и выбирает номера, которые нам нужны. Во многих случаях это должно работать весьма быстро. 

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)