Выберите n случайных строк из таблицы SQL Server

309

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

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

Кто-нибудь когда-нибудь делал это раньше? Любые идеи?

Джон М Гант
источник
3
В MSDN есть хорошая статья, в которой освещаются многие из этих проблем: Случайный выбор строк из большого стола
KyleMit,
Возможный дубликат Как запросить случайную строку в SQL?
Мусульманин Бен Дхау

Ответы:

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

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

Ральф Шиллингтон
источник
1
Мне нравится этот подход гораздо лучше, чем использование статьи, на которую он ссылался.
ДжошБерке
14
Всегда полезно помнить, что newid () не очень хороший генератор псевдослучайных чисел, по крайней мере, не так хорошо, как rand (). Но если вам просто нужны какие-то неопределенно случайные выборки и вас не волнуют математические качества и тому подобное, это будет достаточно. В противном случае вам нужно: stackoverflow.com/questions/249301/…
user12861
1
Хм, извините, если это очевидно .. но что это значит [yourPk]? РЕДАКТИРОВАТЬ: НВМ, понял это ... Первичный ключ. Durrr
Snailer
4
newid - guid призван быть уникальным, но не случайным .. неправильный подход
Brans Ds
2
например, при большом количестве строк более 1 млн. newid()стоимость ввода-вывода Sort Estimate будет очень высокой и повлияет на производительность.
aadi1295
81

В зависимости от ваших потребностей, 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 .

Патрик Тейлор
источник
7
Как указывает Роб Бук ниже, сглаживание результатов в таблицах сгущает результаты, и поэтому не является хорошим способом получить небольшое количество случайных результатов
Оскар Аустегард
Вы не возражаете против вопроса, как это работает: выберите верхний 1 процент * из порядка [tablename] с помощью newid (), поскольку newid () не является столбцом в [tablename]. Добавляет ли SQL Server внутренний столбец newid () в каждой строке, а затем выполняет сортировку?
FrenkyB
Табличный образец был лучшим ответом для меня, поскольку я делал сложный запрос для очень большой таблицы. Без сомнения, это было удивительно быстро. Я получал изменение в количестве записей, возвращаемых, когда я запускал это несколько раз, но все они были в допустимых пределах погрешности.
jessier3
38

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

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

Для лучшего выполнения истинной случайной выборки лучший способ состоит в том, чтобы отфильтровать строки случайным образом. Я нашел следующий пример кода в электронной документации по SQL Server. Ограничение наборов результатов с помощью 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 должен быть последним средством, если у вас большой набор результатов.

Роб Бок
источник
Я тоже видел эту статью и пробовал ее в своем коде. Кажется, что NewID()она оценивается только один раз, а не в каждой строке, что мне не нравится ...
Эндрю Мао
23

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

  SELECT * FROM Table1
  WHERE (ABS(CAST(
  (BINARY_CHECKSUM(*) *
  RAND()) as int)) % 100) < 10
Кайл Макклеллан
источник
Очень интересно. После прочтения статьи я не очень понимаю, почему RAND()не возвращает одно и то же значение для каждой строки (что противоречит BINARY_CHECKSUM()логике). Это потому, что он вызывается внутри другой функции, а не является частью предложения SELECT?
Джон М Гант
Этот запрос выполнялся для таблицы с 6-миллиметровыми строками менее чем за секунду.
Марк Мелвилл
2
Я выполнил этот запрос на таблице с 35 записями и продолжал иметь две из них в наборе результатов очень часто. Это может быть проблемой rand()или комбинацией вышеперечисленного - но я отказался от этого решения по этой причине. Кроме того, количество результатов варьировалось от 1 до 5, поэтому это может быть также неприемлемо в некоторых сценариях.
Оливер
Разве RAND () не возвращает одинаковое значение для каждой строки?
Sarsaparilla
RAND()возвращает одно и то же значение для каждой строки (вот почему это решение быстрое). Однако строки с двоичными контрольными суммами, которые находятся очень близко друг к другу, подвергаются высокому риску получения аналогичных результатов контрольной суммы, вызывая комкование, когда RAND()оно мало. Например, (ABS(CAST((BINARY_CHECKSUM(111,null,null) * 0.1) as int))) % 100== SELECT (ABS(CAST((BINARY_CHECKSUM(113,null,null) * 0.1) as int))) % 100. Если ваши данные страдают от этой проблемы, умножьте BINARY_CHECKSUMна 9923.
Brian
12

Эта ссылка имеет интересное сравнение между 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 .

RJardines
источник
2
Привет, Deumber, приятно нашел, вы можете конкретизировать его, так как ответы, содержащие только ссылки, вероятно, будут удалены.
бумми
1
@bummi Я изменил его, чтобы избежать ответа только по ссылке :)
QMaster
Это лучший ответ. «ORDER BY NEWID ()» работает в большинстве случаев (таблицы меньшего размера), но, как показывают тесты в ссылочной ссылке, она отстает с
ростом
10

Если вам (в отличие от 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

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

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

ОБНОВИТЬ

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

Даниэль Брюкнер
источник
10
Причина использования «всех приведений и всех математических вычислений» заключается в улучшении производительности.
ХКФ
6

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

SELECT TOP [number] 
FROM table_name
ORDER BY RAND(CHECKSUM(*) * RAND())
Нанки
источник
3

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

SELECT `PRIMARY_KEY`, rand() FROM table ORDER BY rand() LIMIT 5000;
Джефф Ферланд
источник
3
Это не будет работать. Поскольку оператор выбора является атомарным, он захватывает только одно случайное число и дублирует его для каждой строки. Вы должны были бы повторно посеять это в каждом ряду, чтобы заставить это измениться.
Том Х
4
Ммм ... люблю различия между поставщиками. Выбор является атомарным на MySQL, но я полагаю по-другому. Это будет работать в MySQL.
Джефф Ферланд
2

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

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

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

klyd
источник
Есть ли преимущество использования специального @seed против RAND ()?
QMaster
абсолютно, вы использовали параметр seed и заполняете его параметром date, функция RAND () делает то же самое, за исключением использования полного значения времени, я хочу знать, есть ли какое-то преимущество в использовании удобного созданного параметра, такого как seed, выше RAND () или нет?
QMaster
Ах !. ОК, это было требование проекта. Мне нужно было создать список из n-случайных строк детерминированным способом. В основном руководство хотело знать, какие «случайные» строки мы будем выбирать за несколько дней до того, как строки были выбраны и обработаны. Построив начальное значение на основе года / месяца, я могу гарантировать, что при любом вызове запроса этот год будет возвращать тот же «случайный» список. Я знаю, это было странно, и, возможно, были лучшие способы, но это сработало ...
klyd
ХАХА :) Понятно, но я думаю, что общий смысл случайно выбранных записей - это не одни и те же записи в разных текущих запросах.
QMaster,
1

Попробуй это:

SELECT TOP 10 Field1, ..., FieldN
FROM Table1
ORDER BY NEWID()
Рави Парашар
источник
0

Похоже, newid () не может использоваться в предложении where, поэтому для этого решения требуется внутренний запрос:

SELECT *
FROM (
    SELECT *, ABS(CHECKSUM(NEWID())) AS Rnd
    FROM MyTable
) vw
WHERE Rnd % 100 < 10        --10%
сарсапарель
источник
0

Я использовал его в подзапросе, и он вернул мне те же строки в подзапросе

 SELECT  ID ,
            ( SELECT TOP 1
                        ImageURL
              FROM      SubTable 
              ORDER BY  NEWID()
            ) AS ImageURL,
            GETUTCDATE() ,
            1
    FROM    Mytable

Затем я решил с включением родительской переменной таблицы, где

SELECT  ID ,
            ( SELECT TOP 1
                        ImageURL
              FROM      SubTable 
              Where Mytable.ID>0
              ORDER BY  NEWID()
            ) AS ImageURL,
            GETUTCDATE() ,
            1
    FROM    Mytable

Обратите внимание, где условия

VISHMAY
источник
0

Используемый язык обработки на стороне сервера (например, PHP, .net и т. Д.) Не указан, но если это PHP, возьмите требуемое число (или все записи) и вместо рандомизации в запросе используйте PHP-функцию shuffle. Я не знаю, если .net имеет эквивалентную функцию, но если она есть, то используйте ее, если вы используете .net

ORDER BY RAND () может привести к значительному снижению производительности в зависимости от того, сколько записей задействовано.

SpacePhoenix
источник
Я не помню точно, для чего я использовал это в то время, но я, вероятно, работал в C #, возможно на сервере, или, возможно, в клиентском приложении, не уверен. C # не имеет ничего, прямо сопоставимого с shuffle afaik в PHP, но это можно сделать, применив функции из объекта Random в операции Select, упорядочив результат, а затем взяв первые десять процентов. Но нам нужно прочитать всю таблицу с диска на сервере БД и передать ее по сети, только чтобы отбросить 90% этих данных. Обработка его непосредственно в БД почти наверняка более эффективна.
Джон М Гант
-2

Это работает для меня:

SELECT * FROM table_name
ORDER BY RANDOM()
LIMIT [number]
глубоко
источник
9
@ user537824, вы пробовали это на SQL Server? RANDOM не является функцией, а LIMIT не является ключевым словом. Синтаксис SQL Server для того, что вы делаете, был бы select top 10 percent from table_name order by rand(), но это также не работает, потому что rand () возвращает одинаковое значение во всех строках.
Джон М Гант