Выберите n случайных строк из таблицы SQL Server - PullRequest
284 голосов
/ 11 мая 2009

У меня есть таблица SQL Server, содержащая около 50 000 строк. Я хочу выбрать около 5000 из этих строк в случайном порядке. Я придумал сложный способ: создать временную таблицу со столбцом «случайное число», скопировать в нее свою таблицу, перебрать временную таблицу и обновить каждую строку с помощью RAND(), а затем выбрать из этой таблицы случайный номер столбца <0,1. Я ищу более простой способ сделать это, в одном выражении, если это возможно. </p>

В этой статье предлагается использовать функцию NEWID(). Это выглядит многообещающе, но я не вижу, как можно надежно выбрать определенный процент строк.

Кто-нибудь когда-нибудь делал это раньше? Есть идеи?

Ответы [ 15 ]

357 голосов
/ 11 мая 2009
select top 10 percent * from [yourtable] order by newid()

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

select  * from [yourtable] where [yourPk] in 
(select top 10 percent [yourPk] from [yourtable] order by newid())

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

77 голосов
/ 12 мая 2009

В зависимости от ваших потребностей, TABLESAMPLE даст вам почти случайную и лучшую производительность. это доступно на сервере MS SQL 2005 и позже.

TABLESAMPLE будет возвращать данные со случайных страниц вместо случайных строк и, следовательно, даже не будет извлекать данные, которые не будут возвращены.

На очень большом столе, который я тестировал

select top 1 percent * from [tablename] order by newid()

заняло более 20 минут.

select * from [tablename] tablesample(1 percent)

заняло 2 минуты.

Производительность также улучшится на небольших выборках в TABLESAMPLE, тогда как не будет newid().

Пожалуйста, имейте в виду, что это не так случайно, как метод newid(), но даст вам достойную выборку.

См. Страницу MSDN .

37 голосов
/ 28 мая 2009

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

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 должен быть последним средством, если у вас большой набор результатов.

21 голосов
/ 06 сентября 2012

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

  SELECT * FROM Table1
  WHERE (ABS(CAST(
  (BINARY_CHECKSUM(*) *
  RAND()) as int)) % 100) < 10
9 голосов
/ 26 сентября 2012

Если вам (в отличие от OP) требуется определенное количество записей (что затрудняет подход к CHECKSUM) и вы хотите получить более случайную выборку, чем сама TABLESAMPLE, а также хотите иметь более высокую скорость, чем CHECKSUM, вы можете обойтись объединение методов TABLESAMPLE и NEWID (), например:

DECLARE @sampleCount int = 50
SET STATISTICS TIME ON

SELECT TOP (@sampleCount) * 
FROM [yourtable] TABLESAMPLE(10 PERCENT)
ORDER BY NEWID()

SET STATISTICS TIME OFF

В моем случае это самый простой компромисс между случайностью (это не совсем, я знаю) и скоростью. Изменяйте процент TABLESAMPLE (или строки) в зависимости от ситуации - чем выше процент, тем более случайная выборка, но ожидайте линейного падения скорости. (Обратите внимание, что TABLESAMPLE не примет переменную)

9 голосов
/ 11 мая 2009

Просто упорядочите таблицу по случайному числу и получите первые 5000 строк, используя TOP.

SELECT TOP 5000 * FROM [Table] ORDER BY newid();

UPDATE

Только что попробовал, и достаточно newid() вызова - нет необходимости во всех приведениях и в математике.

8 голосов
/ 16 октября 2014

Эта ссылка имеет интересное сравнение между Orderby (NEWID ()) и другими методами для таблиц с 1, 7 и 13 миллионами строк.

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

SELECT TOP 10 PERCENT *
  FROM Table1
  ORDER BY NEWID()

Однако запрос NEWID имеет большой недостаток, когда вы используете его для больших таблиц. Предложение ORDER BY приводит к копированию всех строк в таблице в базу данных tempdb, где они сортируются. Это вызывает две проблемы:

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

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

SELECT * FROM Table1
  WHERE (ABS(CAST(
  (BINARY_CHECKSUM(*) *
  RAND()) as int)) % 100) < 10

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

Пожалуйста, прочитайте статью полностью в MSDN .

4 голосов
/ 16 октября 2015

Это комбинация первоначальной исходной идеи и контрольной суммы, которая, как мне кажется, дает правильные случайные результаты без затрат NEWID ():

SELECT TOP [number] 
FROM table_name
ORDER BY RAND(CHECKSUM(*) * RAND())
4 голосов
/ 11 мая 2009

В MySQL вы можете сделать это:

SELECT `PRIMARY_KEY`, rand() FROM table ORDER BY rand() LIMIT 5000;
2 голосов
/ 29 июля 2014

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

Для 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 */

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

...