быстрый выбор случайной строки из большой таблицы в mysql

Как быстро выбрать случайную строку из большой таблицы mysql?

Я работаю на php, но меня интересует любое решение, даже если оно на другом языке.


person lajos    schedule 17.10.2008    source источник


Ответы (23)


Возьмите все идентификаторы, выберите из них случайный и получите полную строку.

Если вы знаете, что идентификаторы последовательны без дыр, вы можете просто взять максимум и вычислить случайный идентификатор.

Если здесь и там есть дыры, но в основном последовательные значения, и вас не волнует слегка искаженная случайность, возьмите максимальное значение, вычислите идентификатор и выберите первую строку с идентификатором, равным или превышающим тот, который вы вычислили. Причина перекоса в том, что идентификаторы, следующие за такими дырами, будут иметь больше шансов быть выбранными, чем те, которые следуют за другим идентификатором.

Если вы заказываете случайным образом, у вас будет ужасное сканирование таблицы, и слово быстро не применимо к такому решению.

Не делайте этого и не заказывайте по GUID, у него та же проблема.

person Lasse V. Karlsen    schedule 17.10.2008

Я знал, что должен быть способ сделать это в одном запросе быстрым способом. И вот оно:

Быстрый способ без привлечения внешнего кода, респект

http://jan.kneschke.de/projects/mysql/order-by-rand/

SELECT name
  FROM random AS r1 JOIN
       (SELECT (RAND() *
                     (SELECT MAX(id)
                        FROM random)) AS id)
        AS r2
 WHERE r1.id >= r2.id
 ORDER BY r1.id ASC
 LIMIT 1;
person Vinko Vrsalovic    schedule 17.10.2008
comment
Обратите внимание на компромисс, заключающийся в том, что, чтобы быть уверенным в получении результата с первой попытки, с большей вероятностью будут выбраны любые клавиши, которым предшествуют пробелы. например, при наличии двух записей с ключами 1 и 10 запись с ключом 10 будет выбрана в 90% случаев. - person Dave Sherohman; 17.10.2008
comment
Да, вы можете получить лучшее распределение, если ключи будут без пробелов и избегают предложений WHERE и ORDER BY. Прочтите статью, там все очень хорошо описано. Я не хотел воровать все это, поэтому не стал ставить другие вопросы, плюсы и минусы каждого. - person Vinko Vrsalovic; 17.10.2008
comment
Этот запрос каким-то образом не возвращает данные в какой-то момент, когда вы указываете дополнительный параметр, например WHERE r1.id ›= r2.id AND r1.some_field=1, в то время как some_field содержит данные=1. Любая идея о том, как решить эту проблему? - person lomse; 18.02.2015

MediaWiki использует интересный прием (для функции Википедии Special:Random): в таблице со статьями есть дополнительный столбец со случайным числом (генерируется при создании статьи). Чтобы получить случайную статью, сгенерируйте случайное число и получите статью со следующим большим или меньшим (не помню каким) значением в столбце случайных чисел. С индексом это может быть очень быстро. (А MediaWiki написана на PHP и разработана для MySQL.)

Этот подход может вызвать проблему, если результирующие числа плохо распределены; IIRC, это было исправлено в MediaWiki, поэтому, если вы решите сделать это таким образом, вам следует взглянуть на код, чтобы увидеть, как это делается в настоящее время (вероятно, они периодически регенерируют столбец случайных чисел).

person CesarB    schedule 18.10.2008
comment
Это прекрасная идея. Есть ли статья или другой ресурс с подробным описанием этого? - person Agnel Kurian; 10.05.2011
comment
это хорошая идея, но для N желаемые результаты могут не сработать, я думаю. Потому что вы можете получить меньше результатов или порядок может быть таким же. - person GorillaApe; 21.10.2012
comment
Это хорошая идея. Но в запросе нам все еще нужно сортировать по случайному столбцу, верно? Предположим, что случайным столбцом является random_number, тогда запрос выглядит так: SELECT * FROM mytable WHERE random_number›$rand ORDER BY random_number LIMIT 1. Это намного быстрее, чем ORDER BY RAND()? - person haibuihoang; 21.04.2014
comment
Вам нужно будет установить степень ограничения на максимальное ваше случайное число в отношении текущего количества записей. Затем увеличивайте этот предел со степенью корреляции с количеством строк в таблице по мере его роста. Например, когда записей не так много. Скажем, у вас есть 3. Без ограничения на случайное число вы можете получить, скажем, 2 очень маленьких числа и одно большое. Самый маленький из 3-х почти никогда не будет вызван, когда разрыв между минимальным, самим собой и средним числом настолько мал. Что, если min=0, max=100 с 3 записями и назначенными номерами рандов 49, 50, 51? - person 1''; 11.06.2015
comment
Я этого не понимаю. Чем это отличается от простого рандомизации числа между 1 и max(id) и выбора записи с этим ID? Зачем нужна дополнительная колонка? - person user2906759; 01.09.2015
comment
Это действительно отличная идея, лайк! - person Kai Burghardt; 31.01.2017
comment
Создайте индекс для столбца, содержащего случайное число. Затем SELECT id FROM table WHERE randNumb >= RAND() ORDER BY randNumb LIMIT 5 будет ускорено индексирование. Если вы используете MyISAM, вам может понадобиться составной индекс для (randNumb, id). - person O. Jones; 05.05.2017
comment
э. это чрезвычайно предвзято. рассмотрим начальный набор с тремя страницами: одна случайная оказалась на 0,1, другая на 0,3 и последняя на 0,9. это означает, что у человека есть шанс 0,1; у одного шанс 0,2; у одного шанс 0,6; и последний шанс 0,1 распределяется, однако он обрабатывает проигрыш в верхней строке. в лучшем случае это 6-кратное искажение; худшее это 7x. но со временем выровняется! взгляд на индекс показывает, что после почти двух десятилетий пребывания в качестве одного из крупнейших сайтов в мире это не так. на самом деле со временем ситуация становится хуже, так как почти совпадения сокращают существование других страниц. ужасная идея - person John Haugeland; 06.08.2017

Вот решение, которое работает довольно быстро и обеспечивает лучшее случайное распределение, не зависящее от того, являются ли значения id непрерывными или начинаются с 1.

SET @r := (SELECT ROUND(RAND() * (SELECT COUNT(*) FROM mytable)));
SET @sql := CONCAT('SELECT * FROM mytable LIMIT ', @r, ', 1');
PREPARE stmt1 FROM @sql;
EXECUTE stmt1;
person Bill Karwin    schedule 17.10.2008
comment
Как получить строку, возвращаемую этим SQL-запросом, с помощью PHP? Установка $query равным приведенному выше, а затем выполнение обычного mysql_query($query) не возвращает никаких результатов. Спасибо. - person ProgrammerGirl; 12.04.2012
comment
Это 1,5 сканирования таблицы — 1 для COUNT(*) (при условии InnoDB), что меньше, чем полное сканирование для OFFSET @r. Но он превосходен в том, что он случайный и не зависит от свойств идентификатора. - person Rick James; 07.08.2015
comment
@RickJames, правильно. Другим решением было бы перечислить строки с новым столбцом, заполненным последовательными целыми числами. Затем можно получить наибольшее значение с помощью MAX() вместо COUNT(), а затем выбрать его по индексу, не справляясь с пробелами. Хотя это решение требует перенумерации по мере появления и исчезновения строк. - person Bill Karwin; 07.08.2015

Возможно, вы могли бы сделать что-то вроде:

SELECT * FROM table 
  WHERE id=
    (FLOOR(RAND() * 
           (SELECT COUNT(*) FROM table)
          )
    );

Это предполагает, что все ваши идентификационные номера являются последовательными без пробелов.

person davr    schedule 26.09.2008
comment
На самом деле вам может понадобиться CEIL вместо FLOOR, зависит от того, начинается ли ваш идентификатор с 0 или 1. - person davr; 27.09.2008
comment
Это предполагает, что выражение кэшируется и не пересчитывается для каждой строки. - person BCS; 27.09.2008
comment
В первичном ключе есть пробелы, так как некоторые строки удаляются. - person David; 27.09.2008

Добавьте столбец, содержащий вычисленное случайное значение, в каждую строку и используйте его в предложении упорядочения, ограничившись одним результатом при выборе. Это работает быстрее, чем сканирование таблицы, которое вызывает ORDER BY RANDOM().

Обновление: вам по-прежнему необходимо вычислить некоторое случайное значение перед выдачей оператора SELECT при извлечении, конечно, например.

SELECT * FROM `foo` WHERE `foo_rand` >= {some random value} LIMIT 1
person Rob    schedule 26.09.2008
comment
Я думала об этом. Добавьте новый индексированный столбец и при создании строки назначьте ему случайное целое число. Но проблема в том, что я храню ненужные данные, и вам все равно придется делать что-то еще, чтобы фактически получить из нее случайную строку, поскольку данные случайного столбца являются статическими. - person David; 27.09.2008
comment
Почему это -2, а у Цезаря Б +17? Мне они кажутся примерно одинаковыми. - person Jarrod Mosen; 14.05.2012
comment
Должно ли это быть SELECT * FROM foo WHERE foo_rand ›= {некоторое случайное значение} ORDER BY foo_rand LIMIT 1? - person haibuihoang; 21.04.2014
comment
Что делать, если ваше {некоторое случайное значение} больше, чем самое высокое предварительно сгенерированное случайное число в таблице. Вы вернете пустой набор записей. - person Codemonkey; 25.06.2018

Есть еще один способ создать случайные строки, используя только запрос и без порядка с помощью rand(). Он включает в себя пользовательские переменные. См. как создавать случайные строки из таблицы

person Ilan Hazan    schedule 30.11.2009

Чтобы найти случайные строки из таблицы, не используйте ORDER BY RAND(), потому что это заставляет MySQL выполнять полную сортировку файлов и только затем извлекать требуемое предельное количество строк. Чтобы избежать полной сортировки файлов, используйте функцию RAND() только в предложении where. Он остановится, как только достигнет необходимого количества строк. См. http://www.rndblog.com/how-to-select-random-rows-in-mysql/

person Sagi Bron    schedule 25.01.2011

если вы не удаляете строку в этой таблице, наиболее эффективным способом является:

(если вы знаете идентификатор mininum, просто пропустите его)

SELECT MIN(id) AS minId, MAX(id) AS maxId FROM table WHERE 1

$randId=mt_rand((int)$row['minId'], (int)$row['maxId']);

SELECT id,name,... FROM table WHERE id=$randId LIMIT 1
person parm.95    schedule 31.05.2010

Я вижу здесь много решений. Одно или два кажутся нормальными, но другие решения имеют некоторые ограничения. Но следующее решение будет работать для всех ситуаций

select a.* from random_data a, (select max(id)*rand() randid  from random_data) b
     where a.id >= b.randid limit 1;

Здесь, id, не нужно быть последовательным. Это может быть любой первичный ключ/уникальный/автоинкрементный столбец. См. следующее Самый быстрый способ выбрать случайную строку из большой таблицы MySQL

Спасибо Zillur - www.techinfobest.com

person Zillur    schedule 22.02.2014

Для выбора нескольких случайных строк из заданной таблицы (скажем, «слов») наша команда придумала вот такую ​​красоту:

SELECT * FROM
`words` AS r1 JOIN 
(SELECT  MAX(`WordID`) as wid_c FROM `words`) as tmp1
WHERE r1.WordID >= (SELECT (RAND() * tmp1.wid_c) AS id) LIMIT n
person Community    schedule 23.04.2009

Классический «SELECT id FROM table ORDER BY RAND() LIMIT 1» на самом деле в порядке.

См. следующий отрывок из руководства по MySQL:

Если вы используете LIMIT row_count с ORDER BY, MySQL завершит сортировку, как только найдет первые строки row_count отсортированного результата, а не сортирует весь результат.

person igelkott    schedule 27.09.2008
comment
Но он все равно должен присваивать случайный номер каждой записи, не так ли? Я спрашиваю, потому что это объяснение не имеет для меня особого смысла: как он собирается возвращать первые N отсортированных строк, если весь набор результатов не отсортирован: S - person Damir Zekić; 26.10.2008
comment
@igelkott, все еще есть проблема с производительностью, я думаю, это не нормально - person Unreality; 03.11.2009

При заказе вы сделаете полный скан таблицы. Лучше всего, если вы сделаете выбор счетчика (*), а затем получите случайную строку = rownum между 0 и последним реестром.

person MazarD    schedule 17.10.2008

Простым, но медленным способом будет (хорошо для небольших таблиц)

SELECT * from TABLE order by RAND() LIMIT 1
person Vinko Vrsalovic    schedule 17.10.2008
comment
Это создаст случайное значение для всех строк в таблице, сортировку, а затем захват одной строки. Это не быстро. - person Lasse V. Karlsen; 17.10.2008
comment
Истинный. Хотя время разработки быстрое. (и во время ответа :-)). Я оставлю это здесь для пользователей, не занимающихся большими столами, которым это может понадобиться. - person Vinko Vrsalovic; 17.10.2008
comment
smallish может быть на удивление маленьким (у меня были проблемы с таблицей записей размером 20 КБ на виртуальном хосте), и отслеживание такого рода проблем может быть королевской головной болью. Сделайте себе одолжение и с самого начала используйте правильный алгоритм. - person Creshal; 21.06.2013
comment
Это приведет к значительному падению производительности для больших таблиц. Проверьте этот аналогичный вопрос stackoverflow.com/questions/1244555/ - person iankit; 27.01.2014

В псевдокоде:

sql "select id from table"
store result in list
n = random(size of list)
sql "select * from table where id=" + list[n]

Это предполагает, что id является уникальным (первичным) ключом.

person Anders Sandvig    schedule 17.10.2008
comment
Если идентификаторы не меняются часто, вы можете даже сохранить список идентификаторов в памяти, чтобы ускорить работу. - person Anders Sandvig; 17.10.2008
comment
Что, если есть миллиард строк? Это означает, что ваша переменная списка огромна. - person Bill Karwin; 17.10.2008

Взгляните на эту ссылку Яна Кнешке или этот ответ SO, поскольку они оба обсуждают тот же вопрос. В ответе SO также рассматриваются различные варианты и есть несколько хороших предложений в зависимости от ваших потребностей. Ян рассматривает все различные варианты и рабочие характеристики каждого из них. Он заканчивает следующим образом для наиболее оптимизированного метода, с помощью которого можно сделать это в MySQL select:

SELECT name
  FROM random AS r1 JOIN
       (SELECT (RAND() *
                     (SELECT MAX(id)
                        FROM random)) AS id)
        AS r2
 WHERE r1.id >= r2.id
 ORDER BY r1.id ASC
 LIMIT 1;

ХТХ,

-Дипин

person Dipin    schedule 20.03.2009

Я немного новичок в SQL, но как насчет генерации случайного числа в PHP и использования

SELECT * FROM the_table WHERE primary_key >= $randNr

это не решает проблему с дырками в таблице.

Но вот поворот предложения lassevks:

SELECT primary_key FROM the_table

Используйте mysql_num_rows() в PHP для создания случайного числа на основе приведенного выше результата:

SELECT * FROM the_table WHERE primary_key = rand_number

Кстати, обратите внимание, насколько медленным является SELECT * FROM the_table:
создание случайного числа на основе mysql_num_rows() и последующее перемещение указателя данных в эту точку mysql_data_seek(). Насколько медленным это будет для больших таблиц, скажем, с миллионом строк?

person Community    schedule 19.12.2008

Я столкнулся с проблемой, когда мои идентификаторы не были последовательными. Что это я придумал.

SELECT * FROM products WHERE RAND()<=(5/(SELECT COUNT(*) FROM products)) LIMIT 1

Возвращаемых строк примерно 5, но я ограничиваю их до 1.

Если вы хотите добавить еще одно предложение WHERE, это становится немного интереснее. Допустим, вы хотите найти товары со скидкой.

SELECT * FROM products WHERE RAND()<=(100/(SELECT COUNT(*) FROM pt_products)) AND discount<.2 LIMIT 1

Что вам нужно сделать, так это убедиться, что вы возвращаете достаточно результатов, поэтому я установил его на 100. Наличие предложения WHERE Discount‹.2 в подзапросе было в 10 раз медленнее, поэтому лучше возвращать больше результатов и ограничение.

person bikedorkseattle    schedule 30.05.2012

Используйте приведенный ниже запрос, чтобы получить случайную строку

SELECT user_firstname ,
COUNT(DISTINCT usr_fk_id) cnt
FROM userdetails 
GROUP BY usr_fk_id 
ORDER BY cnt ASC  
LIMIT 1
person MANOJ    schedule 24.02.2015

В моем случае моя таблица имеет идентификатор в качестве первичного ключа, автоинкремент без пробелов, поэтому я могу использовать COUNT(*) или MAX(id) для получения количества строк.

Я сделал этот скрипт для проверки самой быстрой работы:

logTime();
query("SELECT COUNT(id) FROM tbl");
logTime();
query("SELECT MAX(id) FROM tbl");
logTime();
query("SELECT id FROM tbl ORDER BY id DESC LIMIT 1");
logTime();

Результаты:

  • Количество: 36.8418693542479 ms
  • Максимум: 0.241041183472 ms
  • Заказ: 0.216960906982 ms

Ответ с методом заказа:

SELECT FLOOR(RAND() * (
    SELECT id FROM tbl ORDER BY id DESC LIMIT 1
)) n FROM tbl LIMIT 1

...
SELECT * FROM tbl WHERE id = $result;
person António Almeida    schedule 15.05.2015

Я использовал это, и задание было выполнено по ссылке из здесь

SELECT * FROM myTable WHERE RAND()<(SELECT ((30/COUNT(*))*10) FROM myTable) ORDER BY RAND() LIMIT 30;
person Yousef Altaf    schedule 12.03.2016

Создайте функцию, чтобы сделать это, скорее всего, лучший ответ и самый быстрый ответ здесь!

Плюсы - Работает даже с гапсами и очень быстро.

<?

$sqlConnect = mysqli_connect('localhost','username','password','database');

function rando($data,$find,$max = '0'){
   global $sqlConnect; // Set as mysqli connection variable, fetches variable outside of function set as GLOBAL
   if($data == 's1'){
     $query = mysqli_query($sqlConnect, "SELECT * FROM `yourtable` ORDER BY `id` DESC LIMIT {$find},1");

     $fetched_data = mysqli_fetch_assoc($query);
      if(mysqli_num_rows($fetched_data>0){
       return $fetch_$data;
      }else{
       rando('','',$max); // Start Over the results returned nothing
      }
   }else{
     if($max != '0'){
        $irand = rand(0,$max); 
        rando('s1',$irand,$max); // Start rando with new random ID to fetch
     }else{

        $query = mysqli_query($sqlConnect, "SELECT `id` FROM `yourtable` ORDER BY `id` DESC LIMIT 0,1");
        $fetched_data = mysqli_fetch_assoc($query);
        $max = $fetched_data['id'];
        $irand = rand(1,$max);
        rando('s1',$irand,$max); // Runs rando against the random ID we have selected if data exist will return
     }
   }
 }

 $your_data = rando(); // Returns listing data for a random entry as a ASSOC ARRAY
?>

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

person RandomGuest    schedule 29.03.2017

Быстрый и грязный метод:

SET @COUNTER=SELECT COUNT(*) FROM your_table;

SELECT PrimaryKey
FROM your_table
LIMIT 1 OFFSET (RAND() * @COUNTER);

Сложность первого запроса составляет O(1) для таблиц MyISAM.

Второй запрос сопровождает полное сканирование таблицы. Сложность = О (n)

Грязный и быстрый метод:

Только для этой цели заведите отдельную таблицу. Вы также должны вставлять одни и те же строки в эту таблицу при каждой вставке в исходную таблицу. Предположение: нет DELETE.

CREATE TABLE Aux(
  MyPK INT AUTO_INCREMENT,
  PrimaryKey INT
);

SET @MaxPK = (SELECT MAX(MyPK) FROM Aux);
SET @RandPK = CAST(RANDOM() * @MaxPK, INT)
SET @PrimaryKey = (SELECT PrimaryKey FROM Aux WHERE MyPK = @RandPK);

Если DELETE разрешены,

SET @delta = CAST(@RandPK/10, INT);

SET @PrimaryKey = (SELECT PrimaryKey
                   FROM Aux
                   WHERE MyPK BETWEEN @RandPK - @delta AND @RandPK + @delta
                   LIMIT 1);

Общая сложность O(1).

person yogman    schedule 18.10.2008