edit

Выборка случайной записи в MySQL и PostgreSQL

event Wed 03 Jun '09
label базы данных, постгря, SQL, мускул
language ru
code code

Довольно частая задача — выборка набора случайных записей из БД.

Если погуглить можно найти кучу различных способов.

Самый очевидный SELECT * FROM table ORDER BY RAND(); не выдерживает никакой критики, так как на больших таблицах будет жутко тормозить по вполне очевидным причинам.

Другой вариант — вытащить число записей COUNT(*)-ом, взять рандомное число от 1 до этого числа и организовать LIMIT <рамндом>, 1, но этот способ не катит, если нужно несколько записей: тут либо несколько запросов подряд делать надо, либо делать их же, но UNION-ом.

В Гугле есть ещё несколько вариантов (попробуйте поискать сами, есть интересные для разных типов СУБД), но вариант, изобретённый в своё время мной, я там не нашёл, поэтому кладу его тут:

SELECT * FROM table WHERE RAND() > 0.5 LIMIT 10;

Его фишка в том, что он в реальности намного быстрее, чем о том говорит EXPLAIN, и вот почему: выборка прекращается очень рано за счёт LIMIT-а, так что вся таблица выбираться не будет. Сортировки нет, поэтому будет простой скан без файл-сортинга (как в случае с ORDER BY RAND(), который к тому же мускулу придётся вычислить для каждой записи), а RAND() будет посчитан только для немногих записей, которые мускул успеет пройти до полного формирования результата запроса. Полный скан у нас будет в любом случае, раз нет особо выборки с участием индексов, но избавление от сортировки и вычисления функции для каждой строки — огромный шаг вперёд!

У данного способа, однако, есть один недостаток: довольно низкое качество случайности выборки. По большому счёту этот запрос опирается на тезис о том, что если сортировка не задана, то мускул вернёт записи в неопределённом порядке, как попало, что я здесь принимаю за случайный порядок, а это на самом деле не совсем так: опыты показывают, что первыми возвращаются как правило последние изменённые записи (хотя я могу ошибаться, пусть гуру поправят), так что чем новее запись тем больше у неё шансов попасть в выборку. А записи ближе к центру и концу таблицы попадут в выборку с наименьшей вероятностью, стремящейся к нулю к последним записям. Это отчасти можно скомпенсировать оперируя числом, с которым сравнивается RAND() (в моём случае это 0.5): чем оно больше, тем «дальше» успеет пройти мускул до полного формирования нужной выборки.

В общем, если вам нужно быстро выбрать несколько случайных записей из огромной таблицы и вас устраивает качество рандома, которое даёт этот запрос, то можете его смело использовать :) Он до сих пор трудится на благо человечетсва на одном из сайтов, к которым я приложил руку :)

Кстати, впервые он был опробован на PostgreSQL-е, так что можете пользовать и на нём.