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

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

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

Peter O. 15.09.2014 14:58
Освоение архитектуры микросервисов с Laravel: Лучшие практики, преимущества и советы для разработчиков
Освоение архитектуры микросервисов с Laravel: Лучшие практики, преимущества и советы для разработчиков
В последние годы архитектура микросервисов приобрела популярность как способ построения масштабируемых и гибких приложений. Laravel , популярный PHP...
Как построить CRUD-приложение в Laravel
Как построить CRUD-приложение в Laravel
Laravel - это популярный PHP-фреймворк, который позволяет быстро и легко создавать веб-приложения. Одной из наиболее распространенных задач в...
Освоение PHP и управление базами данных: Создание собственной СУБД - часть II
Освоение PHP и управление базами данных: Создание собственной СУБД - часть II
В предыдущем посте мы создали функциональность вставки и чтения для нашей динамической СУБД. В этом посте мы собираемся реализовать функции обновления...
Документирование API с помощью Swagger на Springboot
Документирование API с помощью Swagger на Springboot
В предыдущей статье мы уже узнали, как создать Rest API с помощью Springboot и MySql .
Роли и разрешения пользователей без пакета Laravel 9
Роли и разрешения пользователей без пакета Laravel 9
Этот пост изначально был опубликован на techsolutionstuff.com .
Как установить LAMP Stack - Security 5/5 на виртуальную машину Azure Linux VM
Как установить LAMP Stack - Security 5/5 на виртуальную машину Azure Linux VM
В предыдущей статье мы завершили установку базы данных, для тех, кто не знает.
47
1
30 282
24
Перейти к ответу Данный вопрос помечен как решенный

Ответы 24

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

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

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

На самом деле вы можете захотеть CEIL вместо FLOOR, в зависимости от того, начинается ли ваш идентификатор с 0 или 1

davr 27.09.2008 02:18

Это предполагает, что выражение кэшируется и не пересчитывается для каждой строки.

BCS 27.09.2008 02:24

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

David 27.09.2008 02:30

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

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

SELECT * FROM `foo` WHERE `foo_rand` >= {some random value} LIMIT 1

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

David 27.09.2008 02:31

Почему это -2, а у Сезара Би +17? Мне они кажутся почти такими же.

Jarrod Mosen 14.05.2012 02:24

Должен ли он быть «ВЫБРАТЬ * ИЗ foo ГДЕ foo_rand> = {какое-то случайное значение} ЗАКАЗАТЬ ПО foo_rand LIMIT 1»?

haibuihoang 21.04.2014 18:22

Что делать, если ваше {некоторое случайное значение} больше, чем наивысшее заранее сгенерированное случайное число в таблице. Вы вернете пустой набор записей.

Codemonkey 25.06.2018 15:19

Классический "ВЫБРАТЬ идентификатор ИЗ таблицы ORDER BY RAND () LIMIT 1" на самом деле подходит.

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

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

Но он все равно должен присвоить случайный номер каждой записи, не так ли? Я спрашиваю, потому что это объяснение не имеет для меня особого смысла: как он будет возвращать первые N отсортированных строк, если весь набор результатов не отсортирован: S

Damir Zekić 26.10.2008 02:46

@igelkott, все еще проблема с производительностью, я думаю, это не нормально

Unreality 03.11.2009 05:26

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

SELECT * from TABLE order by RAND() LIMIT 1

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

Lasse V. Karlsen 17.10.2008 11:43

Правда. Тем не менее, это быстрое время разработки. (и во время ответа :-)). Я оставлю его здесь для пользователей небольших таблиц, которым он может понадобиться

Vinko Vrsalovic 17.10.2008 11:49

"smallish" может быть на удивление маленьким (я столкнулся с проблемами с таблицей ввода 20k на виртуальном хосте), и отслеживание такого рода проблем может быть головной болью королевский. Сделайте себе одолжение и с самого начала используйте правильный алгоритм.

Creshal 21.06.2013 16:03

Это приведет к большой потере производительности для больших таблиц. Отметьте этот аналогичный вопрос stackoverflow.com/questions/1244555/…

iankit 27.01.2014 10:26
Ответ принят как подходящий

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

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

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

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

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

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

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

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

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

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

Anders Sandvig 17.10.2008 11:54

Что, если есть миллиард строк? Это означает, что ваша переменная списка огромна.

Bill Karwin 17.10.2008 22:23

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

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

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;

Обратите внимание на компромисс: чтобы быть уверенным в получении результата с первой попытки, с большей вероятностью будут выбраны любые ключи, которым предшествуют пробелы. Например, для двух записей с ключами 1 и 10 запись с ключом 10 будет выбрана в 90% случаев.

Dave Sherohman 17.10.2008 14:49

Да, вы можете получить лучшее распределение, если ключи будут без пробелов и без предложений WHERE и ORDER BY. Проверьте статью, там все довольно хорошо объяснено. Я не хотел воровать все это, поэтому не стал задавать другие вопросы, плюсы и минусы каждого из них.

Vinko Vrsalovic 17.10.2008 16:21

Этот запрос почему-то не возвращает данные в какой-то момент, когда вы указываете какой-то дополнительный параметр, например WHERE r1.id> = r2.id AND r1.some_field = 1, в то время как some_field содержит data = 1. Есть идеи, как это решить?

lomse 18.02.2015 16:09

Вот решение, которое работает довольно быстро и получает лучшее случайное распределение, независимо от того, являются ли значения 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;

Как получить строку, возвращаемую этим SQL-запросом, с помощью PHP? Установка $query, как указано выше, а затем выполнение обычного mysql_query($query) не возвращает никаких результатов. Спасибо.

ProgrammerGirl 12.04.2012 03:00

Это 1,5 сканирования таблицы - 1 для COUNT(*) (при условии InnoDB), что-то меньшее, чем полное сканирование для OFFSET @r. Но он отлично работает случайным образом и не зависит от свойств идентификатора.

Rick James 07.08.2015 00:41

@RickJames, верно. Другое решение - пронумеровать строки новым столбцом, заполненным серийными целыми числами. Тогда можно получить максимальное значение с помощью MAX () вместо COUNT (), а затем выбрать его по индексу, не пытаясь справиться с пробелами. Хотя это решение требует перенумерации по мере того, как строки приходят и уходят.

Bill Karwin 07.08.2015 05:29

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

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

Это прекрасная идея. Есть ли статья или другой ресурс, подробно описывающий это?

Agnel Kurian 10.05.2011 23:22

это хорошая идея, но для N желаемых результатов может не сработать, я думаю, потому что вы можете получить меньше результатов или порядок может быть таким же.

GorillaApe 21.10.2012 23:25

Хорошая идея. Но в запросе нам все равно нужно сортировать по случайному столбцу, верно? Предположим, что случайный столбец - это random_number, тогда запрос будет иметь вид: «SELECT * FROM mytable WHERE random_number> $ rand ORDER BY random_number LIMIT 1». Это намного быстрее, чем ORDER BY RAND ()?

haibuihoang 21.04.2014 18:20

Вам нужно будет установить степень ограничения на максимальное количество случайных чисел относительно текущего количества записей. Затем увеличивайте этот предел со степенью корреляции с количеством строк в таблице по мере его увеличения. Пример - когда записей мало. Скажем, у вас есть 3. Без ограничения случайного числа вы можете, скажем, иметь 2 очень маленьких числа и одно большое. Наименьшее из трех почти никогда не будет вызвано, когда разрыв между минимальным, самим и средним числом настолько мал. Что делать, если min = 0, max = 100 с 3 записями, а присвоенным rand # было 49, 50, 51?

1'' 11.06.2015 03:46

Я этого не понимаю. Чем это отличается от случайного выбора числа от 1 до max (id) и выбора записи с этим идентификатором? Зачем нужен лишний столбец?

user2906759 01.09.2015 10:42

Это действительно отличная идея, в восторге!

Kai Burghardt 31.01.2017 19:08

Создайте индекс для столбца, содержащего случайное число. Тогда SELECT id FROM table WHERE randNumb >= RAND() ORDER BY randNumb LIMIT 5 получит индексное ускорение. Если вы используете MyISAM, вам может потребоваться составной индекс на (randNumb, id).

O. Jones 05.05.2017 23:36

э. это смещение очень сильно. рассмотрим исходный набор из трех страниц: одна случайная оказалась на 0,1, другая - на 0,3, а последняя - на 0,9. это означает, что шанс равен 0,1; у одного шанс 0,2; у одного шанс 0,6; и последний шанс 0,1 распределяется, однако он обрабатывает убытки верхней линии. в лучшем случае - 6-кратное искажение; худшее это 7х. "но со временем это выровняется!" взгляд на индекс показывает, что после почти двух десятилетий существования одного из крупнейших сайтов на Земле, этого не произошло. он фактически получает хуже с течением времени, поскольку близкие совпадения сокращают существование других страниц. ужасная идея

John Haugeland 06.08.2017 20:36

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

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

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

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

Второй запрос сопровождает полное сканирование таблицы. Сложность = O (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).

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

SELECT * FROM the_table WHERE primary_key >= $randNr

это не решает проблемы с отверстиями в столе.

Но вот поворот в предложении лассевков:

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(). Насколько медленно это будет происходить на больших таблицах с миллионом строк?

Взгляните на эта ссылка Яна Кнешке или этот ТАК ответ, поскольку они оба обсуждают один и тот же вопрос. Ответ 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;

HTH,

-Дипин

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

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

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

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

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

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

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

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

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 раз медленнее, поэтому лучше возвращать больше результатов и лимит.

SELECT DISTINCT * FROM yourTable WHERE 4 = 4 LIMIT 1;

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

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

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

Спасибо Зиллур - www.techinfobest.com

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

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

В моем случае у моей таблицы есть идентификатор в качестве первичного ключа, автоинкремент без пробелов, поэтому я могу использовать 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;

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

SELECT * FROM myTable WHERE RAND()<(SELECT ((30/COUNT(*))*10) FROM myTable) ORDER BY RAND() LIMIT 30;

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

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

<?

$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
?>

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

Другие вопросы по теме