MySQL быстро выбирает 10 случайных строк из 600К строк


463

Как я могу лучше всего написать запрос, который выбирает 10 строк случайным образом из общего числа 600 КБ?


15
Вот 8 техник ; возможно, один будет хорошо работать в вашем случае.
Рик Джеймс

Ответы:


386

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

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

Для наиболее общего случая вот как вы это делаете:

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

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


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

6
Как вы получаете 10 разных случайных строк? Нужно ли устанавливать ограничение в 10, а затем повторять с 10 раз mysqli_fetch_assoc($result)? Или эти 10 результатов не обязательно различимы?
Адам

12
Случайное требует равных шансов для любого результата, на мой взгляд. ;)
lukeocodes

4
В полной статье рассматриваются такие проблемы, как неравное распределение и повторяющиеся результаты.
Брэд Сзонье

1
в частности, если у вас есть пробел в начале ваших идентификаторов, первый будет выбран (мин / макс-мин) времени. Для этого случая простой твик - это MAX () - MIN () * RAND + MIN (), что не слишком медленно.
Код Абоминатор

343
SELECT column FROM table
ORDER BY RAND()
LIMIT 10

Не эффективное решение, но работает


139
ORDER BY RAND()относительно медленно
Матеуш Чаритонюк

7
Mateusz - proof pls, SELECT words, transcription, translation, sound FROM vocabulary WHERE menu_id=$menuId ORDER BY RAND() LIMIT 10занимает 0,0010, без LIMIT 10 - 0,0012 (в этой таблице 3500 слов).
Артур Кушман

26
@zeusakm 3500 слов не так уж много; проблема в том, что он взрывается после определенной точки, потому что MySQL должен фактически сортировать ВСЕ записи после прочтения каждой; как только эта операция коснется жесткого диска, вы почувствуете разницу.
Ja͢ck

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

10
Когда я брал интервью у Facebook в 2010 году, меня спросили, как выбрать случайную запись из огромного файла неизвестного размера за одно чтение. Как только вы придумали идею, ее легко обобщить для выбора нескольких записей. Так что да, сортировка всего файла смешна. В то же время это очень удобно. Я просто использовал этот подход, чтобы выбрать 10 случайных строк из таблицы с более чем 1 000 000 строк. Конечно, мне пришлось немного подождать; но я просто хотел понять, как выглядят типичные строки в этой таблице ...
osa

27

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

SELECT * FROM tbl AS t1 JOIN (SELECT id FROM tbl ORDER BY RAND() LIMIT 10) as t2 ON t1.id=t2.id

Этот запрос на 200K таблице занимает 0.08s и нормальную версию (SELECT * FROM TBL ORDER BY RAND () LIMIT 10) принимает 0.35s на моей машине.

Это быстро, потому что на этапе сортировки используется только индексированный столбец идентификаторов. Вы можете увидеть это поведение в объяснении:

ВЫБЕРИТЕ * ОТ ТАБЛИЧНОГО ЗАКАЗА ПО RAND () LIMIT 10: Простое объяснение

ВЫБЕРИТЕ * ОТ tbl КАК t1 ПРИСОЕДИНЯЙТЕСЬ (ВЫБЕРИТЕ идентификатор ОТ tbl ЗАКАЗАТЬ ПО RAND () LIMIT 10) при t2 ON t1.id = t2.id введите описание изображения здесь

Взвешенная версия : https://stackoverflow.com/a/41577458/893432


1
Извините, я проверил! низкая производительность на 600 тыс. записей.
Дилан Б

@DylanB Я обновил ответ тестом.
Али

17

Я получаю быстрые запросы (около 0,5 секунд) с медленным процессором , выбирая 10 случайных строк в регистрах 400 КБ базы данных MySQL без кэширования размером 2 ГБ. Смотрите здесь мой код: Быстрый выбор случайных строк в MySQL

<?php
$time= microtime_float();

$sql='SELECT COUNT(*) FROM pages';
$rquery= BD_Ejecutar($sql);
list($num_records)=mysql_fetch_row($rquery);
mysql_free_result($rquery);

$sql="SELECT id FROM pages WHERE RAND()*$num_records<20
   ORDER BY RAND() LIMIT 0,10";
$rquery= BD_Ejecutar($sql);
while(list($id)=mysql_fetch_row($rquery)){
    if($id_in) $id_in.=",$id";
    else $id_in="$id";
}
mysql_free_result($rquery);

$sql="SELECT id,url FROM pages WHERE id IN($id_in)";
$rquery= BD_Ejecutar($sql);
while(list($id,$url)=mysql_fetch_row($rquery)){
    logger("$id, $url",1);
}
mysql_free_result($rquery);

$time= microtime_float()-$time;

logger("num_records=$num_records",1);
logger("$id_in",1);
logger("Time elapsed: <b>$time segundos</b>",1);
?>

11
Учитывая мою таблицу с более чем 14 миллионами записей, это так же медленно, какORDER BY RAND()
Фабрицио

5
@snippetsofcode В вашем случае - 400 тыс. строк, которые вы можете использовать просто «ORDER BY rand ()». Ваш трюк с 3 запросами бесполезен. Вы можете переписать его как «ВЫБЕРИТЕ ИД, URL-адрес ОТ страниц, ГДЕ ИДЕНТИФИКАЦИЯ (ВЫБЕРИТЕ ИД ИЗ СТРАНЫ, ЗАКАЗАТЬ rand () LIMIT 10)»
Роман Подлинов

4
Ваша техника все еще выполняет сканирование таблицы. Используйте, FLUSH STATUS; SELECT ...; SHOW SESSION STATUS LIKE 'Handler%';чтобы увидеть это.
Рик Джеймс

4
Также попробуйте выполнить этот запрос на веб-странице 200 req / s. Параллелизм убьет тебя.
Marki555

Преимущество @RomanPodlinov по сравнению с обычным ORDER BY RAND()заключается в том, что он сортирует только идентификаторы (не полные строки), поэтому временная таблица меньше, но все равно должна сортировать их все.
Marki555

16

Это очень простой и однострочный запрос.

SELECT * FROM Table_Name ORDER BY RAND() LIMIT 0,10;

21
К вашему сведению, order by rand()очень медленно, если стол большой
evilReiko

6
Иногда МЕДЛЕННОЕ принимается, если я хочу сохранить его

Индексирование должно применяться к таблице, если оно большое.
Мухаммед Азим

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

13

Из книги:

Выберите случайную строку, используя смещение

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

<?php
$rand = "SELECT ROUND(RAND() * (SELECT COUNT(*) FROM Bugs))";
$offset = $pdo->query($rand)->fetch(PDO::FETCH_ASSOC);
$sql = "SELECT * FROM Bugs LIMIT 1 OFFSET :offset";
$stmt = $pdo->prepare($sql);
$stmt->execute( $offset );
$rand_bug = $stmt->fetch();

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


1
для очень больших таблиц SELECT count(*)становится медленным.
Ганс Z

7

Как выбрать случайные строки из таблицы:

Отсюда: выберите случайные строки в MySQL

Быстрое улучшение по сравнению с «сканированием таблицы» заключается в использовании индекса для выбора случайных идентификаторов.

SELECT *
FROM random, (
        SELECT id AS sid
        FROM random
        ORDER BY RAND( )
        LIMIT 10
    ) tmp
WHERE random.id = tmp.sid;

1
Это помогает некоторым для MyISAM, но не для InnoDB (при условии, что id является кластеризованным PRIMARY KEY).
Рик Джеймс

7

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

Таким образом, одним из решений будет следующее:

SELECT * FROM table WHERE key >= FLOOR(RAND()*MAX(id)) LIMIT 1

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

однако это не случайно, потому что ваши ключи, скорее всего, не будут распределяться равномерно.

Это действительно большая проблема, и ее нелегко решить, выполнив все требования, MySQL rand () - лучшее, что вы можете получить, если вам действительно нужно 10 случайных строк.

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

Вопрос в том, насколько случайным он вам нужен.

Можете ли вы объяснить немного больше, чтобы я мог дать вам хорошее решение.

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

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


Привет джо В этом конкретном случае у ключей не должно быть пробелов, но со временем это может измениться. И пока ваш ответ работает, он сгенерирует случайные 10 строк (при условии, что я напишу предел 10), которые являются последовательными, и я хотел, так сказать, больше случайности. :) Спасибо.
Франциск

Если вам нужно 10, используйте некое объединение для генерации 10 уникальных строк.
Джоно

что я сказал. вам нужно выполнить это 10 раз. объединение его с объединением - один из способов поместить его в один запрос. см. мое приложение 2 минуты назад.
Surrican

1
@TheSurrican, это решение выглядит круто, но очень некорректно . Попробуйте вставить только один очень большой, Idи все ваши случайные запросы вернут вам этот Id.
Pacerier

1
FLOOR(RAND()*MAX(id))склонен к возвращению больших идентификаторов.
Рик Джеймс

3

Мне нужен был запрос, чтобы вернуть большое количество случайных строк из довольно большой таблицы. Это то, что я придумал. Сначала получите максимальный идентификатор записи:

SELECT MAX(id) FROM table_name;

Затем подставьте это значение в:

SELECT * FROM table_name WHERE id > FLOOR(RAND() * max) LIMIT n;

Где max - максимальный идентификатор записи в таблице, а n - количество строк, которые вы хотите в вашем наборе результатов. Предполагается, что в идентификаторах записей нет пробелов, хотя я сомневаюсь, что это повлияет на результат, если они были (хотя я и не пробовал). Я также создал эту хранимую процедуру, чтобы быть более общей; передайте имя таблицы и количество возвращаемых строк. Я использую MySQL 5.5.38 в Windows 2008, 32 ГБ, двойной EHz50 с частотой 3 ГГц и в таблице с 17 361 264 строками, она достаточно стабильна при ~ 0,03 с / 11 с и возвращает 1 000 000 строк. (время из MySQL Workbench 6.1; вы также можете использовать CEIL вместо FLOOR во втором операторе выбора в зависимости от ваших предпочтений)

DELIMITER $$

USE [schema name] $$

DROP PROCEDURE IF EXISTS `random_rows` $$

CREATE PROCEDURE `random_rows`(IN tab_name VARCHAR(64), IN num_rows INT)
BEGIN

SET @t = CONCAT('SET @max=(SELECT MAX(id) FROM ',tab_name,')');
PREPARE stmt FROM @t;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

SET @t = CONCAT(
    'SELECT * FROM ',
    tab_name,
    ' WHERE id>FLOOR(RAND()*@max) LIMIT ',
    num_rows);

PREPARE stmt FROM @t;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END
$$

тогда

CALL [schema name].random_rows([table name], n);

3

Все лучшие ответы уже опубликованы (в основном те, на которые ссылается ссылка http://jan.kneschke.de/projects/mysql/order-by-rand/ ).

Я хочу указать еще одну возможность ускорения - кэширование . Подумайте, почему вам нужно получить случайные строки. Вероятно, вы хотите разместить на сайте какой-нибудь случайный пост или случайную рекламу. Если вы получаете 100 запросов в секунду, действительно ли нужно, чтобы каждый посетитель получал случайные строки? Обычно вполне нормально кэшировать эти X случайных строк в течение 1 секунды (или даже 10 секунд). Не имеет значения, если 100 уникальных посетителей в одну и ту же секунду получат одинаковые случайные записи, потому что в следующую секунду еще 100 посетителей получат другой набор сообщений.

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


3

Я улучшил ответ @Riedsio. Это наиболее эффективный запрос, который я могу найти в большой равномерно распределенной таблице с пробелами (проверено на получение 1000 случайных строк из таблицы, в которой> 2,6 Б строк).

(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max := (SELECT MAX(id) FROM table)) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1)

Позвольте мне распаковать, что происходит.

  1. @max := (SELECT MAX(id) FROM table)
    • Я рассчитываю и сохраняю макс. Для очень больших таблиц есть небольшие издержки для расчета MAX(id)каждый раз, когда вам нужна строка
  2. SELECT FLOOR(rand() * @max) + 1 as rand)
    • Получает случайный идентификатор
  3. SELECT id FROM table INNER JOIN (...) on id > rand LIMIT 1
    • Это заполняет пробелы. В основном, если вы случайно выберете число в промежутках, он просто выберет следующий идентификатор. Предполагая, что промежутки равномерно распределены, это не должно быть проблемой.

Выполнение объединения поможет вам вписать все в один запрос, чтобы избежать выполнения нескольких запросов. Это также позволяет вам сэкономить на расчетах MAX(id). В зависимости от вашего приложения, это может иметь большое значение или очень мало.

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

SELECT t.id, t.name -- etc, etc
FROM table t
INNER JOIN (
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max := (SELECT MAX(id) FROM table)) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1)
) x ON x.id = t.id
ORDER BY t.id

Мне нужно 30 случайных записей, так что я должен изменить , LIMIT 1чтобы LIMIT 30везде в запросе
Хассан

@ Hassaan, вы не должны этого менять, LIMIT 1чтобы LIMIT 30получить 30 записей подряд из случайной точки в таблице. Вместо этого у вас должно быть 30 копий (SELECT id FROM ....части в середине.
Ганс Z

Я пытался, но не кажется более эффективным, чем Riedsioответить. Я пытался с 500 попаданиями в секунду на страницу, используя PHP 7.0.22 и MariaDB на centos 7, с Riedsioответом я получил 500+ дополнительных успешных ответов, затем ваш ответ.
Хасан

1
Ответ @Hassaan riedsio дает 1 строку, этот дает вам n строк, а также сокращает накладные расходы ввода / вывода для запросов. Вы можете получить строки быстрее, но с большей нагрузкой на вашу систему.
Ганс З

3

Я использовал этот http://jan.kneschke.de/projects/mysql/order-by-rand/, опубликованный Riedsio (я использовал случай хранимой процедуры, которая возвращает одно или несколько случайных значений):

   DROP TEMPORARY TABLE IF EXISTS rands;
   CREATE TEMPORARY TABLE rands ( rand_id INT );

    loop_me: LOOP
        IF cnt < 1 THEN
          LEAVE loop_me;
        END IF;

        INSERT INTO rands
           SELECT r1.id
             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;

        SET cnt = cnt - 1;
      END LOOP loop_me;

В этой статье он решает проблему пропусков в идентификаторах, приводящих к не столь случайным результатам, путем ведения таблицы (с использованием триггеров и т. Д. См. Статью); Я решаю проблему, добавив в таблицу еще один столбец, заполненный непрерывными числами, начиная с 1 ( правка: этот столбец добавляется во временную таблицу, созданную подзапросом во время выполнения, не влияет на вашу постоянную таблицу):

   DROP TEMPORARY TABLE IF EXISTS rands;
   CREATE TEMPORARY TABLE rands ( rand_id INT );

    loop_me: LOOP
        IF cnt < 1 THEN
          LEAVE loop_me;
        END IF;

        SET @no_gaps_id := 0;

        INSERT INTO rands
           SELECT r1.id
             FROM (SELECT id, @no_gaps_id := @no_gaps_id + 1 AS no_gaps_id FROM random) AS r1 JOIN
                  (SELECT (RAND() *
                                (SELECT COUNT(*)
                                   FROM random)) AS id)
                   AS r2
            WHERE r1.no_gaps_id >= r2.id
            ORDER BY r1.no_gaps_id ASC
            LIMIT 1;

        SET cnt = cnt - 1;
      END LOOP loop_me;

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


«У меня нет идей, если / насколько мои изменения повлияют на производительность» - довольно много. Для @no_gaps_idиндекса не может быть использован, так что если вы посмотрите на EXPLAINваш запрос, у вас есть Using filesortи Using where(без индекса) для подзапросов, в отличии от исходного запроса.
Фабиан Шменглер,

2

Вот изменитель игры, который может быть полезным для многих;

У меня есть таблица с 200k строк, с последовательными идентификаторами , мне нужно было выбрать N случайных строк, поэтому я решил генерировать случайные значения на основе наибольшего идентификатора в таблице, я создал этот скрипт, чтобы выяснить, какая операция быстрее всего:

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мс
  • Макс: 0.241041183472мс
  • Заказ: 0.216960906982мс

Основываясь на этих результатах, order desc является самой быстрой операцией для получения максимального идентификатора.
Вот мой ответ на вопрос:

SELECT GROUP_CONCAT(n SEPARATOR ',') g FROM (
    SELECT FLOOR(RAND() * (
        SELECT id FROM tbl ORDER BY id DESC LIMIT 1
    )) n FROM tbl LIMIT 10) a

...
SELECT * FROM tbl WHERE id IN ($result);

К вашему сведению: чтобы получить 10 случайных строк из таблицы 200k, мне потребовалось 1,78 мс (включая все операции на стороне php)


3
Предлагаю вам LIMITнемного увеличить - вы можете получить дубликаты.
Рик Джеймс

2

Это супер быстро и на 100% случайно, даже если у вас есть пробелы.

  1. Подсчитайте количество xдоступных вам строкSELECT COUNT(*) as rows FROM TABLE
  2. Выберите 10 различных случайных чисел a_1,a_2,...,a_10от 0 доx
  3. Запросите ваши строки следующим образом: SELECT * FROM TABLE LIMIT 1 offset a_iдля i = 1, ..., 10

Я нашел этот взлом в книге « Антипаттерны SQL» от Билла Карвина .


Я думал о том же решении, скажите, пожалуйста, это быстрее, чем другие методы?
Г.

@ G.Adnane это не быстрее или медленнее, чем принятый ответ, но принятый ответ предполагает равное распределение идентификаторов. Я не могу представить ни одного сценария, где это может быть гарантировано. Это решение находится в O (1), где решение SELECT column FROM table ORDER BY RAND() LIMIT 10находится в O (nlog (n)). Так что да, это быстрое решение, и оно работает для любого распространения идентификаторов.
Адам

нет, потому что в ссылке, размещенной для принятого решения, есть другие методы, я хочу знать, быстрее ли это решение, чем другие, другими способами, мы можем попытаться найти другое, поэтому я спрашиваю, в любом случае, +1 за ваш ответ. Я использовал то же самое
Г.

есть случай, когда вы хотите получить x рядов строк, но смещение переходит в конец таблицы, который возвращает <x строк или только 1 строку. я не видел ваш ответ до того, как опубликовал свой, но я пояснил это здесь stackoverflow.com/a/59981772/10387008
ZOLDIK

@ZOLDIK кажется, что вы выбираете первые 10 строк после смещения x. Я бы сказал, что это не случайное поколение из 10 строк. В моем ответе вы должны выполнить запрос в шаге три 10 раз, то есть каждый получает только одну строку за выполнение и не должен беспокоиться, если смещение находится в конце таблицы.
Адам

1

Если у вас есть только один запрос на чтение

Объедините ответ @redsio с temp-таблицей (600K не так уж много):

DROP TEMPORARY TABLE IF EXISTS tmp_randorder;
CREATE TABLE tmp_randorder (id int(11) not null auto_increment primary key, data_id int(11));
INSERT INTO tmp_randorder (data_id) select id from datatable;

А затем возьмите версию @redsios. Ответ:

SELECT dt.*
FROM
       (SELECT (RAND() *
                     (SELECT MAX(id)
                        FROM tmp_randorder)) AS id)
        AS rnd
 INNER JOIN tmp_randorder rndo on rndo.id between rnd.id - 10 and rnd.id + 10
 INNER JOIN datatable AS dt on dt.id = rndo.data_id
 ORDER BY abs(rndo.id - rnd.id)
 LIMIT 1;

Если стол большой, вы можете просеять первую часть:

INSERT INTO tmp_randorder (data_id) select id from datatable where rand() < 0.01;

Если у вас много запросов на чтение

  1. Версия: Вы можете сохранить таблицу tmp_randorderпостоянной, назовите ее datatable_idlist. Повторно создавайте эту таблицу через определенные промежутки времени (день, час), так как она также будет иметь дыры. Если ваш стол становится действительно большим, вы также можете заполнить отверстия

    выберите l.data_id как целое из списка данных lidatatatable dt для dt.id = l.data_id, где dt.id равен нулю;

  2. Версия: Дайте вашему набору данных столбец random_sortorder либо непосредственно в datatable, либо в постоянной дополнительной таблице datatable_sortorder. Индексируйте этот столбец. Создайте случайное значение в вашем приложении (я назову это $rand).

    select l.*
    from datatable l 
    order by abs(random_sortorder - $rand) desc 
    limit 1;

Это решение различает «крайние строки» с самым высоким и самым низким random_sortorder, поэтому переставляйте их с интервалами (один раз в день).


1

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

SELECT d.* FROM (
SELECT  t.*,  @rownum := @rownum + 1 AS rank
FROM mytable AS t,
    (SELECT @rownum := 0) AS r,
    (SELECT @cnt := (SELECT RAND() * (SELECT COUNT(*) FROM mytable))) AS n
) d WHERE rank >= @cnt LIMIT 10;

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

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

SELECT * FROM (
SELECT d.* FROM (
    SELECT  c.*,  @rownum := @rownum + 1 AS rank
    FROM buildbrain.`commits` AS c,
        (SELECT @rownum := 0) AS r,
        (SELECT @cnt := (SELECT RAND() * (SELECT COUNT(*) FROM buildbrain.`commits`))) AS rnd
) d 
WHERE rank >= @cnt LIMIT 10000 
) t ORDER BY RAND() LIMIT 10;

1

Один способ, который я нахожу довольно хорошим, если есть автоматически сгенерированный идентификатор, это использовать оператор по модулю "%". Например, если вам нужно 10 000 случайных записей из 70 000, вы можете упростить это, сказав, что вам нужна 1 из каждых 7 строк. Это может быть упрощено в этом запросе:

SELECT * FROM 
    table 
WHERE 
    id % 
    FLOOR(
        (SELECT count(1) FROM table) 
        / 10000
    ) = 0;

Если результат деления целевых строк на общее количество не является целым числом, у вас будет несколько дополнительных строк, чем то, что вы просили, поэтому вы должны добавить предложение LIMIT, чтобы помочь вам обрезать результирующий набор следующим образом:

SELECT * FROM 
    table 
WHERE 
    id % 
    FLOOR(
        (SELECT count(1) FROM table) 
        / 10000
    ) = 0
LIMIT 10000;

Это требует полного сканирования, но это быстрее, чем ORDER BY RAND, и, на мой взгляд, проще для понимания, чем другие опции, упомянутые в этой теме. Также, если система, которая записывает в БД, создает наборы строк в пакетах, вы можете не получить такой случайный результат, как ожидали.


2
Теперь, когда я так думаю, если вам нужно каждый раз вызывать случайные строки, это бесполезно. Я думал только о необходимости получить случайные строки из набора, чтобы провести некоторое исследование. Я все еще думаю, что по модулю хорошая вещь, чтобы помочь в другом случае. Вы можете использовать модуль по модулю в качестве фильтра первого прохода, чтобы снизить стоимость операции ORDER BY RAND.
Николас Коэн

1

Если вы хотите одну случайную запись (независимо от того, есть ли пробелы между идентификаторами):

PREPARE stmt FROM 'SELECT * FROM `table_name` LIMIT 1 OFFSET ?';
SET @count = (SELECT
        FLOOR(RAND() * COUNT(*))
    FROM `table_name`);

EXECUTE stmt USING @count;

Источник: https://www.warpconduit.net/2011/03/23/selecting-a-random-record-using-mysql-benchmark-results/#comment-1266


1

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

Если вам нужна предельная простота и скорость при минимальных затратах, то, мне кажется, имеет смысл хранить случайное число для каждой строки в БД. Просто создайте дополнительный столбец random_numberи установите для него значение по умолчанию RAND(). Создайте индекс для этого столбца.

Затем, когда вы хотите извлечь строку, сгенерируйте случайное число в вашем коде (PHP, Perl и т. Д.) И сравните его со столбцом.

SELECT FROM tbl WHERE random_number >= :random LIMIT 1

Я предполагаю, что хотя это очень аккуратно для одной строки, для десяти строк, таких как ОП, вас попросят вызвать его десять раз (или придумать хитрый твик, который сразу ускользает от меня)


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

Спасибо. У меня был сценарий, когда в основной таблице, из которой я хотел получить случайную строку, было 5 миллионов строк и довольно много объединений, и после попытки большинства подходов в этом вопросе это был клудж, на котором я остановился. Одна дополнительная колонка была для меня очень выгодным компромиссом.
Codemonkey

0

Следующее должно быть быстрым, беспристрастным и независимым от столбца id. Однако это не гарантирует, что количество возвращаемых строк будет соответствовать количеству запрошенных строк.

SELECT *
FROM t
WHERE RAND() < (SELECT 10 / COUNT(*) FROM t)

Объяснение: если вы хотите, чтобы 10 строк из 100, то каждая строка имела 1/10 вероятности получения SELECT, чего можно достичь WHERE RAND() < 0.1. Этот подход не гарантирует 10 строк; но если запрос выполняется достаточно раз, среднее число строк на выполнение будет около 10, и каждая строка в таблице будет выбрана равномерно.


0

Вы можете легко использовать случайное смещение с лимитом

PREPARE stm from 'select * from table limit 10 offset ?';
SET @total = (select count(*) from table);
SET @_offset = FLOOR(RAND() * @total);
EXECUTE stm using @_offset;

Вы также можете применить предложение where как

PREPARE stm from 'select * from table where available=true limit 10 offset ?';
SET @total = (select count(*) from table where available=true);
SET @_offset = FLOOR(RAND() * @total);
EXECUTE stm using @_offset;

Протестировано на 600 000 строк (700 МБ). Выполнение запроса таблицы заняло ~ 0,016 с жесткого диска.

--EDIT--
   Смещение может принимать значение, близкое к концу таблицы, что приведет к тому, что оператор select вернет меньше строк (или, возможно, только 1 строка), чтобы избежать этого, мы можем проверить еще offsetраз после объявления этого, например, так

SET @rows_count = 10;
PREPARE stm from "select * from table where available=true limit ? offset ?";
SET @total = (select count(*) from table where available=true);
SET @_offset = FLOOR(RAND() * @total);
SET @_offset = (SELECT IF(@total-@_offset<@rows_count,@_offset-@rows_count,@_offset));
SET @_offset = (SELECT IF(@_offset<0,0,@_offset));
EXECUTE stm using @rows_count,@_offset;

-1

Я использую этот запрос:

select floor(RAND() * (SELECT MAX(key) FROM table)) from table limit 10

время запроса: 0.016с


Имея PK как 1,2,9,15. по вышеуказанному запросу вы получите строки, такие как 4, 7, 14, 11, которых недостаточно!
Джунаид Атари

-2

Вот как я это делаю:

select * 
from table_with_600k_rows
where rand() < 10/600000
limit 10

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


5
Это полное сканирование таблицы, и оно не использует никаких индексов. Для больших столов и занятой среды это большой нет нет.
Мэтт

-2

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

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

Если вы хотите использовать какой-либо оператор соединения и где фильтр вы можете использовать.
Маной

3
Из какой части запроса вы получаете случайность?
Marki555

-4

Я думаю, это самый лучший способ ..

SELECT id, id * RAND( ) AS random_no, first_name, last_name
FROM user
ORDER BY random_no

8
Черт возьми, это один из худших способов получить случайные строки из таблицы. Это полное сканирование таблицы + сортировка файлов + таблица tmp = плохая производительность.
Мэтт

1
Помимо производительности, это также далеко не совершенно случайно; вы упорядочиваете по произведению идентификатора и случайного числа, а не просто по случайному числу, что означает, что строки с более низкими идентификаторами будут смещены в сторону более раннего появления в наборе результатов.
Марк Амери
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.