MySQL вибирає 10 випадкових рядків з 600K рядків швидко


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

Це передбачає, що розподіл ідентифікаторів рівний, і що у списку id можуть бути прогалини. Докладніші приклади див. У статті


52
Так, якщо у вас потенційно великі прогалини в ідентифікаційних номерах, то шанс обрати ваш найнижчий ідентифікатор випадковим чином набагато нижчий, ніж ваші високі ідентифікатори. Насправді шанс на те, що перший ідентифікатор після найбільшого розриву у виборі насправді найвищий. Тому це не є випадковим за визначенням.
lukeocodes

6
Як ви отримуєте 10 різних випадкових рядків? Чи потрібно встановити ліміт 10, а потім повторити 10 разів mysqli_fetch_assoc($result)? Або ці 10 результатів не обов'язково відрізняються?
Адам

12
Випадкові вимагають рівних шансів на будь-який результат, на мій погляд. ;)
lukeocodes

4
Повна стаття стосується таких питань, як нерівні розподіли та повторні результати.
Бредд Шоні

1
конкретно, якщо у вас є пробіл на початку ваших посвідчень особи, перший буде обраний (min / max-min) часу. У цьому випадку простий твік - MAX () - MIN () * RAND + MIN (), який не надто повільний.
Кодовий мерзотник

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

Не ефективне рішення, але працює


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

7
Матеуш - доказ pls, SELECT words, transcription, translation, sound FROM vocabulary WHERE menu_id=$menuId ORDER BY RAND() LIMIT 10займає 0,0010, без обмеження 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,08 секунди, а звичайна версія (ВИБІР * З ТБ ЗАМОВЛЕННЯ ПО РАНДУ () ГРОМИ 10) займає 0,35 секунди на моїй машині.

Це швидко, тому що фаза сортування використовує лише індексований стовпчик ідентифікатора. Ви можете бачити таку поведінку в поясненні:

ВИБІР * З ТБ ЗАМОВЛЕННЯ ПО ГРАНІ 10) Просте пояснення

ВИБІР * ВІД tbl AS t1 ПРИЄДНАЙТЕСЬ (ВИБІР ІД ІЗ tbl ЗАМОВЛЕННЯ ПО ГРАНІ () ГРОМ 10) як t2 НА 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 У вашому випадку - 400k рядків, ви можете використовувати простий "ORDER BY rand ()". Ваш трюк з 3 запитами марний. Ви можете переписати його на кшталт "ВИБРАТИ ідентифікатор, URL-адреса зі сторінок, звідки ідентифікуються (виберіть ідентифікатор зі сторінок ЗАМОВЛЕННЯ rand () ЛІМІТ 10)"
Roman Podlinov

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;

20
FYI, 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 (якщо ІД є кластером PRIMARY KEY).
Рік Джеймс

7

Добре, якщо у вас немає прогалин у ваших клавішах, і всі вони є числовими, ви можете обчислити випадкові числа і вибрати ці рядки. але це, мабуть, не так.

Таким рішенням було б таке:

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

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

однак це НЕ насправді випадково, оскільки ваші ключі, швидше за все, не будуть розподілятися рівномірно.

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

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

Питання в тому, наскільки випадковим воно вам потрібно.

Чи можете ви пояснити трохи більше, щоб я міг дати вам хороше рішення.

Наприклад, у компанії, з якою я працював, було рішення, де вони дуже швидко потребували абсолютної випадковості. Вони закінчилися попереднім заповненням бази даних випадковими значеннями, які були вибрані у спадному порядку і знову встановлені на різні випадкові значення.

Якщо ви навряд чи коли-небудь оновлюєтесь, ви також можете заповнити приріст ідентифікатора, щоб у вас не було прогалин, і ви можете просто обчислити випадкові ключі перед вибором ... Це залежить від випадку використання!


Привіт Джо. У цьому конкретному випадку ключі не повинні мати відсутність прогалин, але з часом це може змінитися. І поки ваша відповідь працює, вона генерує випадкові 10 рядків (за умови, що я пишу обмеження 10), які є послідовними, і я хотів би більше випадковостей, так би мовити. :) Дякую.
Франциск

Якщо вам потрібно 10, використовуйте певний союз для створення 10 унікальних рядків.
johno

tahts що я сказав. вам потрібно виконати це 10 разів. поєднання його wion union - це один із способів розмістити його в одному запиті. дивіться мій додаток 2 хвилини тому.
The 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 Гб, подвійний 3 ГГц E5450, а на таблиці з 17 311 264 рядками це досить послідовно за ~ 0,03 сек / ~ 11 сек, щоб повернути 1 000 000 рядків. (час від MySQL Workbench 6.1; ви також можете використовувати CEIL замість FLOOR у другому операторі select, залежно від ваших уподобань)

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

Я покращив відповідь @Riedsio. Це найефективніший запит, який я можу знайти на великій, рівномірно розподіленій таблиці з пробілами (тестується на отримання 1000 випадкових рядків із таблиці, що має> 2,6B рядків).

(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
    • Це заповнює прогалини. Якщо ви вибрали випадковим чином кількість пропусків, він просто вибере наступний ідентифікатор. Якщо припустити, що розриви розподілені рівномірно, це не повинно бути проблемою.

Здійснення спілки допомагає вам укласти все в 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

@Hassaan ви не повинні, що зміни LIMIT 1до LIMIT 30отримаєте вас 30 записів поспіль від випадкової точки в таблиці. Натомість у вас має бути 30 копій (SELECT id FROM ....деталі посередині.
Hans Z

Я спробував, але не здається більш ефективним, ніж Riedsioвідповісти. Я намагався з 500 за секунду звернень до сторінки, використовуючи PHP 7.0.22 та MariaDB на centos 7, з Riedsioвідповіддю я отримав 500+ додаткових успішних відповідей, а потім вашу відповідь.
Hassaan

1
@Hassaan riedsio відповідає 1 рядок, цей дає вам n рядків, а також скорочує накладні дані вводу / виводу для запитів. Можливо, ви зможете отримати рядки швидше, але з більшим навантаженням на вашу систему.
Hans Z

3

Я використав цей http://jan.kneschke.de/projects/mysql/order-by-rand/, опублікований Рієдсіо (я використовував випадок збереженої процедури, яка повертає одне або більше випадкових значень):

   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мс

Виходячи з цих результатів, опис замовлення - це найшвидша операція, щоб отримати максимальний ідентифікатор,
Ось моя відповідь на питання:

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);

FYI: Щоб отримати 10 випадкових рядків з таблиці 200k, мені знадобилося 1,78 мс (включаючи всі операції на стороні php)


3
Запропонуйте LIMITтрохи збільшити - ви можете отримати дублікати.
Рік Джеймс

2

Усі найкращі відповіді вже розміщені (в основному ті, що посилаються на посилання http://jan.kneschke.de/projects/mysql/order-by-rand/ ).

Хочу визначити ще одну можливість прискорення - кешування . Подумайте, чому вам потрібно отримати випадкові рядки. Можливо, ви хочете відобразити якусь випадкову публікацію чи випадкову рекламу на веб-сайті. Якщо ви отримуєте 100 запитів / с, чи справді потрібно, щоб кожен відвідувач отримував випадкові рядки? Зазвичай ці цілком чудово кешують ці X випадкові рядки протягом 1 секунди (або навіть 10 секунд). Не має значення, якщо 100 унікальних відвідувачів за одну секунду отримують однакові випадкові повідомлення, оскільки наступної секунди ще 100 відвідувачів отримають різний набір публікацій.

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


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 Antipatterns від Білла Карвіна .


Я думав про те саме рішення, скажіть, будь ласка, чи швидше це метод інших?
Г. Аднан

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

ні, тому що у посиланні, розміщеному на прийнятому рішенні, є інші методи, я хочу знати, чи швидше це рішення, ніж інші, інші способи, ми можемо спробувати знайти інший, тому Iam запитує, будь-яким чином, +1 для вашої відповіді. Я використовував самтінг
G. Adnane

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

@ZOLDIK здається, що ви вибираєте перші 10 рядків після зміщення x . Я б заперечив, що це не випадкове покоління з 10 рядів. У моїй відповіді, ви повинні виконати запит на етапі три 10 разів, тобто один отримує лише один рядок за виконання і не потрібно хвилюватися, якщо зміщення знаходиться в кінці таблиці.
Адам

1

Якщо у вас є лише один читання-запит

Поєднайте відповідь @redsio з тимчасовою таблицею (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 Answer:

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 в цілому з datatable_idlist l зліва приєднати дані dt на dt.id = l.data_id, де dt.id недійсний;

  2. Версія: Наведіть свій набір даних у стовпчик random_sortorder або безпосередньо в даних, або в стійкій додатковій таблиці datatable_sortorder . Індексуйте цей стовпець. Створіть у своїй програмі випадкову цінність (я її зателефоную $rand).

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

Це рішення розмежовує "крайові ряди" з найвищим і найнижчим випадковим_порядком, тому переставляйте їх з інтервалом (раз на день).


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;

Для цього потрібне повне сканування, але це швидше, ніж ЗАМОВЛЕННЯ РАНДОМ, і, на мій погляд, простіше зрозуміти, ніж інші варіанти, згадані в цій темі. Також якщо система, яка записує в БД, створює набори рядків у партіях, можливо, ви не отримаєте такого випадкового результату, як ви, де очікуєте.


2
Тепер, коли я так думаю, якщо вам потрібні випадкові рядки щоразу, коли ви його називаєте, це марно. Я думав лише про необхідність отримати випадкові рядки з набору для проведення деяких досліджень. Я все ще думаю, що модуль - це добре допомогти в іншому випадку. Ви можете використовувати модуль як фільтр першого проходу, щоб знизити вартість операції ЗАМОВЛЕННЯ ПО РАНДУ.
Ніколя Коен

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

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


Це насправді дуже приємний та ефективний підхід. Єдиним недоліком є ​​той факт, що ви торгували простором на швидкість, що, на мою думку, здається справною справою.
Tochukwu Nkemdilim

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

0

Далі слід бути швидким, неупередженим та незалежним від стовпця id. Однак це не гарантує, що кількість повернутих рядків буде відповідати кількості запитуваних рядків.

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

Пояснення: якщо припустити, що ви хочете 10 рядків із 100, то кожен рядок має 1/10 ймовірність отримати SELECTed, що може бути досягнуто 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;

Ви також можете застосувати пункт, де так

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сек. HDD-диск

--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.016s


Маючи ПК як 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

Якщо ви хочете використовувати будь-яку заяву приєднання, а також фільтр, який ви можете використовувати.
MANOJ

3
З якої частини запиту ви отримуєте випадковість?
Marki555

-4

Я думаю, це найкращий спосіб ..

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

8
Чорт не, це один з найгірших способів отримати випадкові рядки з таблиці. Це повне сканування таблиці + filesort + tmp table = погана робота.
мат

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