Иногда нужно выбрать строки из базы данных в случайном порядке. При сайтостроительстве эта задача возникает довольно часто — вывести случайную тему из форума, показать в блоке случайный продвигаемый товар, показать случайный ответ посетителя за последнюю неделю и т.п.
Подходов к решению задачи довольно много. Первое решение «в лоб» и чисто средствами MYSQL.
Выборка случайных строк средствами самого MYSQL
Начиная, кажется, с 4й версии mysql стала работать следующая конструкция запроса:
1 |
select * from myTable order by rand() limit N; |
Запрос опирается на случайную сортировку с использованием псевдослучайного генератора чисел функции rand(). При этом запрос выбирает N случайных строк. Очень эффектная конструкция, но производительность запроса сильно падает с ростом размеров выбираемого блока данных. Исходя из целей выборки, можно сократить число предварительно выбираемых строк, задав условия в части запроса ‘where’. Что то вроде :
1 |
select * from myTable where id < 200 order by rand() limit N; |
Понятно, что если мы зададим ‘тяжелые’ условия, связанные с не индексируемыми полями или требующие сложных вычислений, то опять же проиграем в производительности.
Тем не менее, при работе с небольшим числом строк, такой подход вполне приемлем.
Выборка с предварительным расчетом
Что делать, когда Mysql не умеет, или база данных не позволяет делать такие варварские запросы? Будем использовать часть запроса LIMIT. Для начала нам нужно узнать, сколько всего строк в таблице.
1 |
SHOW TABLE STATUS LIKE 'myTable' |
В столбце ‘Rows’ мы получим текущее число строк таблицы. А дальше вычислим произвольный номер из известного уже диапазона и выполним запрос на выборку строки с (псевдо)случайным номером. На PHP это будет выглядеть примерно так:
1 2 3 4 |
$res = mysql_query("SHOW TABLE STATUS LIKE 'myTable'"); $status = mysql_fetch_assoc($res); $NRandom = rand(0, $status['Rows'] - 1); $res = mysql_query("SELECT * FROM `myTable` LIMIT $NRandom, 1"); |
Для того чтобы выбрать N случайных строк, придется выполнить последние две операции N раз, либо, если можно пренебречь случайностью набора строк, выполнить запрос так:
1 |
$res = mysql_query("SELECT * FROM `myTable` LIMIT $NRandom, N"); |
Для большого числа строк, в выбираемом блоке данных, опять же получим снижение быстродействия, особенно для случая N + 1 запросов. Подход более эффективен по сравнению с предыдущим подходом (когда выборка у нас большая) и работает для более ранних версий MySQL, что иногда очень важно.
Двухступенчатая выборка
Этот подход опирается на два ключевых момента и является развитием предыдущего метода. Первое требование — небольшое число строк выборки, а второе — наличие ключевого столбца в таблице. Если первое требование не всегда удаётся удовлетворить в полной мере, то второе условие практически всегда удовлетворяется.
Суть метода. Что если сначала выбрать все идентификаторы подходящих строк средствами СУБД, выбрать из них N случайных идентификаторов средствами PHP (или другого, используемого вами языка), а потом снова средствами СУБД выбрать строки с найденными ID?
Гипотетический пример кода на PHP:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
//$conditions - какие то условия, сокращающие размер выборки, исходя из задачи $res = mysql_query("SELECT `idcolumn` FROM `myTable` WHERE $conditions"); $ids = $idRN = array(); //собираем ID в один массив while ($row = mysql_fetch_array($res)) $ids[] = $row[0]; //выберем N случайных из них $N = xxx; while ($N-- > 0 && count($ids)) { $RN = rand(0, count($ids) - 1); $idRN[] = $ids[$RN]; unset($ids[$RN]); } //делаем ещё один запрос уже для выборки самих данных //(в данном примере считаем, что столбец idcolumn - числовое поле) $res = mysql_query("SELECT * FROM `myTable` WHERE `idcolumn` in (" . implode(',', $idRN) . ")"); |
В таком подходе мы избавляемся от потенциально неэффективного использования части запроса LIMIT. Быстродействие в большей мере зависит от скорости первого запроса и объема выборки, чем от выборки уже самих данных.
Спасибо, автор! Инфа — что надо! Как раз думал над выборками случайных строк. Узнал пару интересных способов.
Вот еще одна похожая статья на эту тему: http://plutov.by/post/order_by_rand_performance
Я вот такой провел эксперимент по ORDER BY: http://plutov.by/post/order_by_rand_performance