Як запросити випадковий рядок у SQL?


510

Як я можу запитати випадковий рядок (або максимально близький до справді випадкового) у чистому SQL?


Я завжди робив це в php після результатів запитів від sql ... це, ймовірно, набагато швидше для обробки відповідно до
додатка


2
Здається, що немає жодного "чистого SQL" рішення, яке працює на всіх dbms ... Є рішення для кожного з них.
Ману

Відповіді:


735

Дивіться цю публікацію: SQL для вибору випадкового рядка з таблиці бази даних . Він проходить методи для цього в MySQL, PostgreSQL, Microsoft SQL Server, IBM DB2 та Oracle (з цього посилання скопійовано наступне):

Виберіть випадковий рядок за допомогою MySQL:

SELECT column FROM table
ORDER BY RAND()
LIMIT 1

Виберіть випадковий рядок за допомогою PostgreSQL:

SELECT column FROM table
ORDER BY RANDOM()
LIMIT 1

Виберіть випадковий рядок за допомогою Microsoft SQL Server:

SELECT TOP 1 column FROM table
ORDER BY NEWID()

Виберіть випадковий рядок з IBM DB2

SELECT column, RAND() as IDX 
FROM table 
ORDER BY IDX FETCH FIRST 1 ROWS ONLY

Виберіть випадковий запис за допомогою Oracle:

SELECT column FROM
( SELECT column FROM table
ORDER BY dbms_random.value )
WHERE rownum = 1

30
-1 для опори order by rand()або еквівалентів у всіх dbs: |. також згадується тут .
AD7six

20
Десять років тому якийсь хлопець сказав, що використовувати ORDER BY RAND()неправильно ...
trejder

ЗАМОВЛЕННЯ NEWID (), схоже, помітно повільніше на SQL Server. Мій запит виглядає так: виберіть топ 1000 C.CustomerId, CL.LoginName від клієнта C внутрішнє з'єднання LinkedAccount LA на C.CustomerId = LA.CustomerId внутрішнє приєднання CustomerLogin CL на C.CustomerId = CL.CustomerId групу C.CustomerId, CL. LoginName, що має кількість (*)> 1 замовлення на NEWID () Видалення рядка "замовлення по NEWID ()", результати повертають набагато швидше.
Бен Пауер

3
Для SQLite використовуйте функцію RANDOM ().
Слам

10
Ці рішення не масштабуються. Вони складаються O(n)з nкількості записів у таблиці. Уявіть, що у вас є 1 мільйон записів, ви дійсно хочете генерувати 1 мільйон випадкових чисел або унікальних ідентифікаторів? Я вважаю за краще використовувати COUNT()і включати це в новому LIMITвиразі з єдиним випадковим числом.
Крістіан Худжер

174

Такі рішення, як Jeremies:

SELECT * FROM table ORDER BY RAND() LIMIT 1

працюють, але їм потрібне послідовне сканування всієї таблиці (тому що випадкове значення, пов'язане з кожним рядком, потрібно обчислити - щоб можна було визначити найменше), що може бути досить повільним навіть для таблиць середнього розміру. Моя рекомендація буде використовувати якийсь індексований числовий стовпець (у багатьох таблицях вони є їх первинними ключами), а потім написати щось на зразок:

SELECT * FROM table WHERE num_value >= RAND() * 
    ( SELECT MAX (num_value ) FROM table ) 
ORDER BY num_value LIMIT 1

Це працює в логарифмічний час, незалежно від розміру таблиці, якщо num_valueвін індексується. Одне застереження: це передбачає, що num_valueрівномірно розподілений у діапазоні 0..MAX(num_value). Якщо ваш набір даних сильно відхилиться від цього припущення, ви отримаєте перекошені результати (деякі рядки з’являться частіше, ніж інші).


8
Друга пропозиція не випадкова. Ви не можете передбачити ряд, який буде обраний, але якби вам довелося зробити ставку, ви зробили ставку на другий ряд. І ви ніколи не зробили ставку на останній рядок, тим менше шансів на те, що ви будете вибирати незалежно від розподілу вашої значущості та наскільки велика ваша таблиця.
Етьєн Расін

1
Я знаю, що зазвичай функції RAND () не дуже якісні, але крім цього, ви можете, будь ласка, пояснити, чому вибір не буде випадковим?
Сіра Пантера

13
Перший - WRONG у SQL Server. Функція RAND () викликається лише один раз на запит, а не один раз у ряд. Тому він завжди вибирає перший рядок (спробуйте).
Код Рейнджера Джеффа Уокера

3
Другий також передбачає, що всі рядки враховуються: можливо, він вибере рядок, який був видалений.
Сем Рюбі

3
@ Sam.Rueby Насправді num_value> = RAND () ... межа 1 гарантує, що порожні рядки будуть пропущені, поки не знайдеться існуючий рядок.
ghord

62

Я не знаю, наскільки це ефективно, але я використовував це раніше:

SELECT TOP 1 * FROM MyTable ORDER BY newid()

Оскільки GUID досить випадкові, впорядкування означає, що ви отримуєте випадковий рядок.


1
Я використовую MS SQL сервер, ВИБІР ТОП 1 * З some_table_name ЗАМОВЛЕННЯ NEWID () чудово працював для мене, дякую за поради, хлопці!

Це саме те саме, щоORDER BY RAND() LIMIT 1
Кен Блум

6
Це також дуже специфічна база даних, оскільки вона використовує TOP 1та newid().
Сірий

12
Це погана ідея. Цей метод не використовуватиме індекс, якщо кожен стовпець не буде індексовано окремо. Таблиця зі 100 мільйонами записів може зайняти дуже багато часу, щоб отримати один запис.
Вимкнути

1
@Switch і яке б рішення ви запропонували?
Акмаль Саліхов

31
ORDER BY NEWID()

займає 7.4 milliseconds

WHERE num_value >= RAND() * (SELECT MAX(num_value) FROM table)

бере 0.0065 milliseconds!

Я обов'язково перейду з останнім методом.


2
Другий варіант не буде вибирати останній ряд. Я не знаю чому - просто вказавши на це.
Волдеморт

7
@Voldemort: rand()повертає число з плаваючою точкою , nде 0 < n < 1. Припускаючи num_value, що це ціле число, повернене значення rand() * max(num_value)також буде приведене до цілого числа, таким чином обрізаючи будь-що після десяткової крапки. Отже, rand() * max(num_value)буде завжди менше max(num_value), тому ніколи не буде обрана останній рядок.
Ян Кемп

Я не буду ефективною, якщо мої дані видаляються часто - якщо я знайду прогалину, мені доведеться повторити весь запит.
Лоїк Коенен

1
@IanKemp Дурне питання, то чому тоді просто не використовувати SELECT MAX (num_value) + 1 ?? Оскільки rand (або RANDOM у більшості випадків) повертає [0,1), ви отримаєте повний діапазон значень. Крім того, так, ти маєш рацію, мусиш виправити запит.
tekHedd

13

Ви не сказали, який сервер використовуєте. У старих версіях SQL Server ви можете користуватися цим:

select top 1 * from mytable order by newid()

У SQL Server 2005 і новіших версіях ви можете використовувати TABLESAMPLEвипадковий зразок, що повторюється:

SELECT FirstName, LastName
FROM Contact 
TABLESAMPLE (1 ROWS) ;

9
MSDN каже, що newid () віддається перевагу над табличним зразком для справді випадкових результатів: msdn.microsoft.com/en-us/library/ms189108.aspx
Andrew Hedges

7
@Andrew Hedges: ЗАМОВИТИ NEWID () занадто дорого
Андрій Ронеа

10

Для SQL Server

newid () / order by буде працювати, але буде коштувати дуже дорого для великих наборів результатів, оскільки він повинен генерувати ідентифікатор для кожного ряду, а потім сортувати їх.

TABLESAMPLE () хороший з точки зору продуктивності, але ви отримаєте сукупність результатів (всі рядки на сторінці будуть повернуті).

Для кращого виконання справжньої випадкової вибірки найкращим способом є фільтрування рядків випадковим чином. У статті SQL Server Books Online я знайшов такий зразок коду Обмеження наборів результатів за допомогою TABLESAMPLE :

Якщо ви дійсно хочете випадкову вибірку окремих рядків, змініть свій запит, щоб фільтрувати рядки випадковим чином, а не використовувати TABLESAMPLE. Наприклад, наступний запит використовує функцію NEWID для повернення приблизно одного відсотка рядків таблиці Sales.SalesOrderDetail:

SELECT * FROM Sales.SalesOrderDetail
WHERE 0.01 >= CAST(CHECKSUM(NEWID(),SalesOrderID) & 0x7fffffff AS float)
              / CAST (0x7fffffff AS int)

Стовпчик SalesOrderID включається у вираз CHECKSUM, щоб NEWID () оцінював один раз у ряд для досягнення вибірки на основі рядка. Вираз CAST (CHECKSUM (NEWID (), SalesOrderID) & 0x7fffffff AS float / CAST (0x7fffffff AS int) обчислюється до значення випадкового поплавця між 0 і 1.

Якщо ви зіткнулися з таблицею з 1 000 000 рядків, ось мої результати:

SET STATISTICS TIME ON
SET STATISTICS IO ON

/* newid()
   rows returned: 10000
   logical reads: 3359
   CPU time: 3312 ms
   elapsed time = 3359 ms
*/
SELECT TOP 1 PERCENT Number
FROM Numbers
ORDER BY newid()

/* TABLESAMPLE
   rows returned: 9269 (varies)
   logical reads: 32
   CPU time: 0 ms
   elapsed time: 5 ms
*/
SELECT Number
FROM Numbers
TABLESAMPLE (1 PERCENT)

/* Filter
   rows returned: 9994 (varies)
   logical reads: 3359
   CPU time: 641 ms
   elapsed time: 627 ms
*/    
SELECT Number
FROM Numbers
WHERE 0.01 >= CAST(CHECKSUM(NEWID(), Number) & 0x7fffffff AS float) 
              / CAST (0x7fffffff AS int)

SET STATISTICS IO OFF
SET STATISTICS TIME OFF

Якщо ви можете піти з використання TABLESAMPLE, це дасть вам найкращі показники. В іншому випадку використовуйте метод newid () / filter. newid () / order by має бути в крайньому випадку, якщо у вас великий набір результатів.


4

Якщо можливо, використовуйте збережені оператори, щоб уникнути неефективності обох індексів на RND () та створення поля номера запису.

ПІДГОТОВИТИ RandomRecord З "SELECT * FROM table LIMIT?, 1";
SET @ n = ПОЛОВИЙ (RAND () * (ВИБРАТИ КОЛЕТ (*) ВІД таблиці));
ВИКОНУЙТЕ RandomRecord USING @n;

Це рішення також бере участь у поверненні випадкових рядків, коли індексоване числове значення, яке використовується у вищезазначеному пункті, не розподілено однаково; тому навіть якщо це займає майже той самий (постійний) час, як і використання, де id_value> = RAND () * MAX (id_value), це краще.
гіда

Наскільки я можу сказати, це не працює в постійний час, воно працює в лінійному часі. У гіршому випадку @n дорівнює кількості рядків у таблиці, а "SELECT * FROM table LIMIT?, 1" оцінює @n - 1 рядок, поки він не потрапить до останнього.
Андрес Ріофріо

3

Найкращим способом є введення випадкового значення в новий стовпець саме для цієї мети та використання чогось подібного (псевдо код + SQL):

randomNo = random()
execSql("SELECT TOP 1 * FROM MyTable WHERE MyTable.Randomness > $randomNo")

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

рішення newid () може зажадати повного сканування таблиці, щоб кожному рядку було призначено нове керівництво, яке буде набагато менш ефективним.

рішення rand () може не працювати взагалі (тобто з MSSQL), оскільки функція буде оцінюватися лише один раз, і кожному рядку буде присвоєно однакове "випадкове" число.


1
Обертання, коли ви отримаєте 0 результатів, дає очевидно випадкову вибірку (не просто "досить хороша"). Це рішення майже масштабує багаторядні запити (подумайте, "партійне переміщення"). Проблема полягає в тому, що результати, як правило, підбираються в одних і тих же групах неодноразово. Щоб обійти це, вам потрібно буде повторно розподілити випадкові числа, які ви тільки що використовували. Ви можете обдурити, відслідковуючи randomNo і встановлюючи його до max (випадковості) результатів, але потім p (рядок i за запитом 1 І рядок i за запитом 2) == 0, що не справедливо. Дозвольте мені зайнятись математикою, і я повернуся до вас із справді справедливою схемою.
alsuren

3

Для SQL Server 2005 та 2008 років, якщо ми хочемо довільну вибірку окремих рядків (із Книг Інтернет ):

SELECT * FROM Sales.SalesOrderDetail
WHERE 0.01 >= CAST(CHECKSUM(NEWID(), SalesOrderID) & 0x7fffffff AS float)
/ CAST (0x7fffffff AS int)

3

Замість використання RAND (), оскільки це не рекомендується , ви можете просто отримати максимальний ідентифікатор (= Max):

SELECT MAX(ID) FROM TABLE;

отримати випадковий між 1..Max (= My_Generated_Random)

My_Generated_Random = rand_in_your_programming_lang_function(1..Max);

а потім запустіть цей SQL:

SELECT ID FROM TABLE WHERE ID >= My_Generated_Random ORDER BY ID LIMIT 1

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

SELECT ID FROM TABLE WHERE ID <= My_Generated_Random ORDER BY ID DESC LIMIT 1

Що буде, якщо генерований випадковий ідентифікатор вже не існує в таблиці? Видалені або пасивні рядки, які ви не хочете показувати користувачеві, можуть спричинити проблеми.
Еблеме

Нічого. Ви отримуєте ЗАКРИТИЙ, а не точний ідентифікаційний номер. Якщо ви вважаєте, що id = 1 видалено, обміняйте 1 мінімумом.
форсберг

2

Як зазначалося у коментарі @ BillKarwin до відповіді @ cnu ...

Поєднуючись з LIMIT, я виявив, що він працює набагато краще (принаймні, з PostgreSQL 9.1), щоб приєднатися до довільного впорядкування, а не безпосередньо замовляти фактичні рядки: наприклад

SELECT * FROM tbl_post AS t
JOIN ...
JOIN ( SELECT id, CAST(-2147483648 * RANDOM() AS integer) AS rand
       FROM tbl_post
       WHERE create_time >= 1349928000
     ) r ON r.id = t.id
WHERE create_time >= 1349928000 AND ...
ORDER BY r.rand
LIMIT 100

Просто переконайтеся, що 'r' генерує значення 'rand' для кожного можливого ключового значення у складному запиті, який приєднаний до нього, але все ж обмежте кількість рядків 'r', де це можливо.

CAST як Integer особливо корисний для PostgreSQL 9.2, який має специфічну оптимізацію сортування для цілочисельних та одноточних плаваючих типів.


1

Більшість рішень тут спрямовані на те, щоб уникнути сортування, але їм все одно потрібно зробити послідовне сканування таблиці.

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

Наступне рішення працює на PostgreSQL 8.4:

explain analyze select * from cms_refs where rec_id in 
  (select (random()*(select last_value from cms_refs_rec_id_seq))::bigint 
   from generate_series(1,10))
  limit 1;

Я вище рішення ви здогадуєтесь 10 різних випадкових значень індексу з діапазону 0 .. [останнє значення id].

Число 10 довільне - ви можете використовувати 100 або 1000, оскільки це (дивно) не має великого впливу на час відповіді.

Також є одна проблема - якщо у вас є рідкісні ідентифікатори, які ви можете пропустити . Рішення полягає у створенні резервного плану :) У цьому випадку чистий старий порядок шляхом випадкового () запиту. У поєднанні ідентифікатор виглядає так:

explain analyze select * from cms_refs where rec_id in 
    (select (random()*(select last_value from cms_refs_rec_id_seq))::bigint 
     from generate_series(1,10))
    union all (select * from cms_refs order by random() limit 1)
    limit 1;

Не пункт про об'єднання ВСІ . У цьому випадку, якщо перша частина повертає будь-які дані, друга НІКОЛИ не виконується!


1

Зрештою, але потрапив сюди через Google, тож для нащадків я додам альтернативне рішення.

Інший підхід - використовувати TOP два рази, з чергуванням замовлень. Я не знаю, чи це "чистий SQL", оскільки він використовує змінну в TOP, але він працює в SQL Server 2008. Ось приклад, який я використовую проти таблиці словникових слів, якщо я хочу випадкове слово.

SELECT TOP 1
  word
FROM (
  SELECT TOP(@idx)
    word 
  FROM
    dbo.DictionaryAbridged WITH(NOLOCK)
  ORDER BY
    word DESC
) AS D
ORDER BY
  word ASC

Звичайно, @idx - це ціле випадкове генерування цілого числа, яке становить від 1 до COUNT (*) на цільовій таблиці включно. Якщо ваш стовпець буде індексований, ви також отримаєте користь від нього. Ще одна перевага полягає в тому, що ви можете використовувати його у функції, оскільки NEWID () заборонено.

Нарешті, вищезазначений запит працює приблизно в 1/10 часу виконання запиту типу NEWID () типу в одній таблиці. YYMV.


1

Ви також можете спробувати скористатися new id()функцією.

Просто напишіть свій запит і використовуйте замовлення за new id()функціями. Це цілком випадково.


1

Для MySQL отримати випадковий запис

 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

Детальніше http://jan.kneschke.de/projects/mysql/order-by-rand/


Після тестування багатьох відповідей я вважаю, що це найкращий варіант. Здається, що це швидко і щоразу підбирає гарне випадкове число. Схоже на другу пропозицію @GreyPanther вище, але ця відповідь набирає більше випадкових чисел.
Джефф Бейкер

1

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

Для MS SQL:

Мінімальний приклад:

select top 10 percent *
from table_name
order by rand(checksum(*))

Нормалізований час виконання: 1.00

Приклад NewId ():

select top 10 percent *
from table_name
order by newid()

Нормалізований час виконання: 1,02

NewId()це незначно повільніше rand(checksum(*)), тому ви, можливо, не захочете використовувати його для великих наборів записів.

Відбір з початковим насінням:

declare @seed int
set @seed = Year(getdate()) * month(getdate()) /* any other initial seed here */

select top 10 percent *
from table_name
order by rand(checksum(*) % seed) /* any other math function here */

Якщо вам потрібно вибрати той самий набір із даним насінням, це, здається, спрацює.


1

У MSSQL (тестовано 11.0.5569) з використанням

SELECT TOP 100 * FROM employee ORDER BY CRYPT_GEN_RANDOM(10)

значно швидше, ніж

SELECT TOP 100 * FROM employee ORDER BY NEWID()

1

У SQL Server ви можете комбінувати TABLESAMPLE з NEWID (), щоб отримати досить гарну випадковість і все одно мати швидкість. Це особливо корисно, якщо ви дійсно бажаєте лише 1 або невеликої кількості рядків.

SELECT TOP 1 * FROM [table] 
TABLESAMPLE (500 ROWS) 
ORDER BY NEWID()

1

За допомогою SQL Server 2012+ ви можете використовувати запит OFFSET FETCH, щоб зробити це для одного випадкового рядка

select  * from MyTable ORDER BY id OFFSET n ROW FETCH NEXT 1 ROWS ONLY

де id - стовпець ідентичності, а n - потрібний рядок - обчислюється як випадкове число між 0 і count () - 1 таблиці (зсув 0 - це перший рядок зрештою)

Це працює з отворами в даних таблиці, якщо у вас є індекс, з яким можна працювати для пункту ORDER BY. Це також дуже добре для випадковості - коли ви працюєте над тим, щоб самі пройти, але нігрів в інших методах немає. Крім того, продуктивність досить хороша, на меншому наборі даних вона добре тримається, хоча я не пробував серйозних тестів на ефективність на кілька мільйонів рядків.



0

Я маю згоду з CD-MaN: Використання "ORDER BY RAND ()" буде добре працювати для невеликих таблиць або коли ви вибираєте лише кілька разів.

Я також використовую техніку "num_value> = RAND () * ...", і якщо я дійсно хочу мати випадкові результати, у таблиці є спеціальний "випадковий" стовпець, який я оновлюю раз на день або близько того. Цей окремий запуск UPDATE займе певний час (особливо тому, що вам доведеться мати індекс у цьому стовпці), але це набагато швидше, ніж створювати випадкові числа для кожного рядка щоразу, коли виконується вибір.


0

Будьте уважні, оскільки TableSample насправді не повертає випадкову вибірку рядків. Він спрямовує ваш запит на перегляд випадкової вибірки сторінок 8 КБ, що складають ваш рядок. Потім ваш запит виконується на даних, що містяться на цих сторінках. Через те, як можна групувати дані на цих сторінках (порядок вставки тощо), це може призвести до даних, які насправді не є випадковою вибіркою.

Дивіться: http://www.mssqltips.com/tip.asp?tip=1308

Ця сторінка MSDN для TableSample містить приклад того, як генерувати фактично випадкову вибірку даних.

http://msdn.microsoft.com/en-us/library/ms189108.aspx


0

Здається, що багато перерахованих ідей все ще використовують замовлення

Однак якщо ви використовуєте тимчасову таблицю, ви зможете призначити випадковий індекс (як і багато запропонованих рішень), а потім схопити перший, який дорівнює довільному числу між 0 і 1.

Наприклад (для DB2):

WITH TEMP AS (
SELECT COMLUMN, RAND() AS IDX FROM TABLE)
SELECT COLUMN FROM TABLE WHERE IDX > .5
FETCH FIRST 1 ROW ONLY

2
Розглянувши це рішення, я знайшов фундаментальний недолік у своїй логіці. Це стало б поверненням тих самих невеликих заданих значень біля початку таблиці, тому що я припускаю, що якщо розподіл повітря було між 0 і 1, існує 50% шансів, що перший рядок відповідатиме цим критеріям.
DAVID


0

Краще рішення для Oracle замість використання dbms_random.value, тоді як для повного замовлення рядків dbms_random.value потрібне повне сканування, і для великих таблиць воно досить повільне.

Використовуйте це замість:

SELECT *
FROM employee sample(1)
WHERE rownum=1


0

Для SQL Server 2005 і вище, розширення відповіді @ GreyPanther на випадки, коли num_valueзначення не має безперервного значення. Це також працює для випадків, коли ми не рівномірно розподіляємо набори даних і коли num_valueце не число, а унікальний ідентифікатор.

WITH CTE_Table (SelRow, num_value) 
AS 
(
    SELECT ROW_NUMBER() OVER(ORDER BY ID) AS SelRow, num_value FROM table
) 

SELECT * FROM table Where num_value = ( 
    SELECT TOP 1 num_value FROM CTE_Table  WHERE SelRow >= RAND() * (SELECT MAX(SelRow) FROM CTE_Table)
)

-1

Випадкова функція від sql може допомогти. Також якщо ви хочете обмежитися лише одним рядком, просто додайте це врешті-решт.

SELECT column FROM table
ORDER BY RAND()
LIMIT 1
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.