Вопрос

Как я могу запросить случайную строку (или как можно более близкую к действительно случайной) в чистом 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

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

Такие решения, как Джереми:

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 NEWID()

берет 7.4 milliseconds

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

берет 0.0065 milliseconds!

Я определенно выберу последний метод.

Вы не сказали, какой сервер используете.В более старых версиях SQL Server вы можете использовать это:

select top 1 * from mytable order by newid()

В SQL Server 2005 и более поздних версиях вы можете использовать TABLESAMPLE чтобы получить повторяемую случайную выборку:

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

Для SQL-сервера

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

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

Для более эффективной истинно случайной выборки лучше всего отфильтровать строки случайным образом.Я нашел следующий пример кода в статье электронной документации по SQL Server. Ограничение наборов результатов с помощью TABLESAMPLE:

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

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

Столбец SalesorderID включен в выражение контрольной суммы, так что NewID () оценивает один раз на строку для достижения отбора проб на основе для одного строки.Выражение выражения (контрольная сумма (newid (), salesordorid) и 0x7ffffffff как 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() и создания поля номера записи.

PREPARE RandomRecord FROM "SELECT * FROM table LIMIT ?,1";
SET @n=FLOOR(RAND()*(SELECT COUNT(*) FROM table));
EXECUTE 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..Макс (=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) JOIN со случайным порядком, а не непосредственное упорядочивание фактических строк:например

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 as 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..[последнее значение идентификатора].

Число 10 произвольное — вы можете использовать 100 или 1000, поскольку оно (что удивительно) не оказывает большого влияния на время отклика.

Есть еще одна проблема - если у вас редкие идентификаторы ты можешь пропустить.Решение состоит в том, чтобы иметь запасной план :) В данном случае чистый старый порядок с помощью запроса Random().Когда объединенный идентификатор выглядит так:

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() в той же таблице.ГГМВ.

Вы также можете попробовать использовать 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()
 SELECT * FROM table ORDER BY RAND() LIMIT 1

Я должен согласиться с CD-MaN:Использование «ORDER BY RAND()» хорошо работает для небольших таблиц или когда вы выполняете SELECT всего несколько раз.

Я также использую технику «num_value >= RAND() * ...», и если я действительно хочу получать случайные результаты, у меня есть специальный «случайный» столбец в таблице, который я обновляю примерно раз в день.Этот одиночный запуск UPDATE займет некоторое время (особенно потому, что вам понадобится индекс для этого столбца), но это намного быстрее, чем создавать случайные числа для каждой строки каждый раз, когда выполняется выборка.

Будьте осторожны, поскольку TableSample на самом деле не возвращает случайную выборку строк.Он направляет ваш запрос на просмотр случайной выборки страниц размером 8 КБ, составляющих вашу строку.Затем ваш запрос выполняется на основе данных, содержащихся на этих страницах.Из-за того, как данные могут быть сгруппированы на этих страницах (порядок вставки и т. д.), это может привести к тому, что данные на самом деле не будут случайной выборкой.

Видеть: http://www.mssqltips.com/tip.asp?tip=1308

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

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

Кажется, что многие из перечисленных идей все еще используют порядок

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

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

Вместо этого используйте это:

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

Для Жар-птицы:

Select FIRST 1 column from table ORDER BY RAND()

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

Для 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
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top