Як я можу запитати випадковий рядок (або максимально близький до справді випадкового) у чистому SQL?
Як я можу запитати випадковий рядок (або максимально близький до справді випадкового) у чистому SQL?
Відповіді:
Дивіться цю публікацію: 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
order by rand()
або еквівалентів у всіх dbs: |. також згадується тут .
ORDER BY RAND()
неправильно ...
O(n)
з n
кількості записів у таблиці. Уявіть, що у вас є 1 мільйон записів, ви дійсно хочете генерувати 1 мільйон випадкових чисел або унікальних ідентифікаторів? Я вважаю за краще використовувати COUNT()
і включати це в новому LIMIT
виразі з єдиним випадковим числом.
Такі рішення, як 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)
. Якщо ваш набір даних сильно відхилиться від цього припущення, ви отримаєте перекошені результати (деякі рядки з’являться частіше, ніж інші).
Я не знаю, наскільки це ефективно, але я використовував це раніше:
SELECT TOP 1 * FROM MyTable ORDER BY newid()
Оскільки GUID досить випадкові, впорядкування означає, що ви отримуєте випадковий рядок.
ORDER BY RAND() LIMIT 1
TOP 1
та newid()
.
ORDER BY NEWID()
займає 7.4 milliseconds
WHERE num_value >= RAND() * (SELECT MAX(num_value) FROM table)
бере 0.0065 milliseconds
!
Я обов'язково перейду з останнім методом.
rand()
повертає число з плаваючою точкою , n
де 0 < n < 1
. Припускаючи num_value
, що це ціле число, повернене значення rand() * max(num_value)
також буде приведене до цілого числа, таким чином обрізаючи будь-що після десяткової крапки. Отже, rand() * max(num_value)
буде завжди менше max(num_value)
, тому ніколи не буде обрана останній рядок.
Ви не сказали, який сервер використовуєте. У старих версіях SQL Server ви можете користуватися цим:
select top 1 * from mytable order by newid()
У SQL Server 2005 і новіших версіях ви можете використовувати TABLESAMPLE
випадковий зразок, що повторюється:
SELECT FirstName, LastName
FROM Contact
TABLESAMPLE (1 ROWS) ;
Для 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 має бути в крайньому випадку, якщо у вас великий набір результатів.
Якщо можливо, використовуйте збережені оператори, щоб уникнути неефективності обох індексів на RND () та створення поля номера запису.
ПІДГОТОВИТИ RandomRecord З "SELECT * FROM table LIMIT?, 1"; SET @ n = ПОЛОВИЙ (RAND () * (ВИБРАТИ КОЛЕТ (*) ВІД таблиці)); ВИКОНУЙТЕ RandomRecord USING @n;
Найкращим способом є введення випадкового значення в новий стовпець саме для цієї мети та використання чогось подібного (псевдо код + SQL):
randomNo = random()
execSql("SELECT TOP 1 * FROM MyTable WHERE MyTable.Randomness > $randomNo")
Це рішення, використовуване кодом MediaWiki. Звичайно, є деякі упередження щодо менших значень, але вони виявили, що достатньо обернути випадкове значення навколо нуля, коли жодні рядки не отримані.
рішення newid () може зажадати повного сканування таблиці, щоб кожному рядку було призначено нове керівництво, яке буде набагато менш ефективним.
рішення rand () може не працювати взагалі (тобто з MSSQL), оскільки функція буде оцінюватися лише один раз, і кожному рядку буде присвоєно однакове "випадкове" число.
Для SQL Server 2005 та 2008 років, якщо ми хочемо довільну вибірку окремих рядків (із Книг Інтернет ):
SELECT * FROM Sales.SalesOrderDetail
WHERE 0.01 >= CAST(CHECKSUM(NEWID(), SalesOrderID) & 0x7fffffff AS float)
/ CAST (0x7fffffff AS int)
Замість використання 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
Як зазначалося у коментарі @ 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, який має специфічну оптимізацію сортування для цілочисельних та одноточних плаваючих типів.
Більшість рішень тут спрямовані на те, щоб уникнути сортування, але їм все одно потрібно зробити послідовне сканування таблиці.
Існує також спосіб уникнути послідовного сканування шляхом переходу на індексне сканування. Якщо ви знаєте значення індексу випадкового рядка, ви можете отримати результат практично миттєво. Проблема полягає в тому, як відгадати значення індексу.
Наступне рішення працює на 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;
Не пункт про об'єднання ВСІ . У цьому випадку, якщо перша частина повертає будь-які дані, друга НІКОЛИ не виконується!
Зрештою, але потрапив сюди через 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.
Ви також можете спробувати скористатися new id()
функцією.
Просто напишіть свій запит і використовуйте замовлення за new id()
функціями. Це цілком випадково.
Для 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/
Ще не дуже бачив цю варіацію у відповідях. У мене було додаткове обмеження, де мені потрібно, давши початкове насіння, щоразу вибирати один і той же набір рядків.
Для 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 */
Якщо вам потрібно вибрати той самий набір із даним насінням, це, здається, спрацює.
У MSSQL (тестовано 11.0.5569) з використанням
SELECT TOP 100 * FROM employee ORDER BY CRYPT_GEN_RANDOM(10)
значно швидше, ніж
SELECT TOP 100 * FROM employee ORDER BY NEWID()
У SQL Server ви можете комбінувати TABLESAMPLE з NEWID (), щоб отримати досить гарну випадковість і все одно мати швидкість. Це особливо корисно, якщо ви дійсно бажаєте лише 1 або невеликої кількості рядків.
SELECT TOP 1 * FROM [table]
TABLESAMPLE (500 ROWS)
ORDER BY NEWID()
За допомогою 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. Це також дуже добре для випадковості - коли ви працюєте над тим, щоб самі пройти, але нігрів в інших методах немає. Крім того, продуктивність досить хороша, на меншому наборі даних вона добре тримається, хоча я не пробував серйозних тестів на ефективність на кілька мільйонів рядків.
SELECT * FROM table ORDER BY RAND() LIMIT 1
Я маю згоду з CD-MaN: Використання "ORDER BY RAND ()" буде добре працювати для невеликих таблиць або коли ви вибираєте лише кілька разів.
Я також використовую техніку "num_value> = RAND () * ...", і якщо я дійсно хочу мати випадкові результати, у таблиці є спеціальний "випадковий" стовпець, який я оновлюю раз на день або близько того. Цей окремий запуск UPDATE займе певний час (особливо тому, що вам доведеться мати індекс у цьому стовпці), але це набагато швидше, ніж створювати випадкові числа для кожного рядка щоразу, коли виконується вибір.
Будьте уважні, оскільки TableSample насправді не повертає випадкову вибірку рядків. Він спрямовує ваш запит на перегляд випадкової вибірки сторінок 8 КБ, що складають ваш рядок. Потім ваш запит виконується на даних, що містяться на цих сторінках. Через те, як можна групувати дані на цих сторінках (порядок вставки тощо), це може призвести до даних, які насправді не є випадковою вибіркою.
Дивіться: http://www.mssqltips.com/tip.asp?tip=1308
Ця сторінка MSDN для TableSample містить приклад того, як генерувати фактично випадкову вибірку даних.
Здається, що багато перерахованих ідей все ще використовують замовлення
Однак якщо ви використовуєте тимчасову таблицю, ви зможете призначити випадковий індекс (як і багато запропонованих рішень), а потім схопити перший, який дорівнює довільному числу між 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
Простий та ефективний спосіб від http://akinas.com/pages/en/blog/mysql_random_row/
SET @i = (SELECT FLOOR(RAND() * COUNT(*)) FROM table); PREPARE get_stmt FROM 'SELECT * FROM table LIMIT ?, 1'; EXECUTE get_stmt USING @i;
Для 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)
)
Випадкова функція від sql може допомогти. Також якщо ви хочете обмежитися лише одним рядком, просто додайте це врешті-решт.
SELECT column FROM table
ORDER BY RAND()
LIMIT 1