Случайная запись из таблицы базы данных (T-SQL)

85

Есть ли краткий способ получить случайную запись из таблицы sql-сервера?

Я хотел бы рандомизировать свои данные модульного теста, поэтому ищу простой способ выбрать случайный идентификатор из таблицы. На английском языке выбор будет «Выбрать один идентификатор из таблицы, где идентификатор - случайное число между самым низким идентификатором в таблице и самым высоким идентификатором в таблице».

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

Идеи?

Джереми
источник
здесь есть пара методов brettb.com/SQL_Help_Random_Numbers.asp
Mesh
2
Вы уверены, что хотите применить этот подход? Данные модульного теста не должны быть случайными - на самом деле вы должны быть уверены, что получите одинаковые результаты независимо от того, сколько раз вы выполняете модульный тест. Наличие случайных данных может нарушить этот фундаментальный принцип модульного тестирования.
повод
Ссылка выше от @Mesh больше не активна.
Роберт Сиверс

Ответы:

146

Есть ли краткий способ получить случайную запись из таблицы сервера sql?

да

SELECT TOP 1 * FROM table ORDER BY NEWID()

Объяснение

Для NEWID()каждой строки создается A, а затем таблица сортируется по нему. Возвращается первая запись (т. Е. Запись с «наименьшим» GUID).

Ноты

  1. Идентификаторы GUID генерируются как псевдослучайные числа начиная с четвертой версии:

    UUID версии 4 предназначен для генерации UUID из истинно случайных или псевдослучайных чисел.

    Алгоритм следующий:

    • Установите два старших бита (биты 6 и 7) clock_seq_hi_and_reserved равными нулю и единице соответственно.
    • Установите четыре старших бита (биты с 12 по 15) поля time_hi_and_version на 4-битный номер версии из Раздела 4.1.3.
    • Установите для всех остальных битов произвольно (или псевдослучайно) выбранные значения.

    - Пространство имен URN универсального уникального идентификатора (UUID) - RFC 4122

  2. Альтернатива SELECT TOP 1 * FROM table ORDER BY RAND()не сработает, как можно было бы подумать. RAND()возвращает одно значение для каждого запроса, поэтому все строки будут иметь одно и то же значение.

  3. Хотя значения GUID являются псевдослучайными, для более требовательных приложений вам понадобится лучший PRNG.

  4. Типичная производительность составляет менее 10 секунд для примерно 1 000 000 строк - конечно, в зависимости от системы. Обратите внимание, что достичь индекса невозможно, поэтому производительность будет относительно ограниченной.

Скливвз
источник
Именно то, что я искал. У меня было ощущение, что это было проще, чем я делал.
Джереми
1
Вы предполагаете, что NEWID производит псевдослучайные значения. Есть большая вероятность, что будут получены последовательные значения. NEWID просто производит уникальные значения. Однако RAND производит псевдослучайные значения.
Skizz
Я запускаю его в сильно индексированной таблице с 1 671 145 строками, и для возврата требуется 7 секунд. Таблица также довольно оптимальна - это фактически сердце нашей базы данных, поэтому о ней позаботились.
Том Риттер
@ ÂviewAnew. 1,6 миллиона строк и 7 секунд для выбора, который не (и не может) попадает в индекс, - это неплохо.
Sklivvz
7
@Skizz, rand так не работает. ОДИНОЧНОЕ случайное значение генерируется перед SELECT. Поэтому, если вы попробуете «SELECT TOP 10 RAND () ...», вы всегда получите одно и то же значение
Sklivvz
27

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

SELECT  TOP 1 *
FROM YourTable
TABLESAMPLE (1000 ROWS)
ORDER BY NEWID()

По- ORDER BY NEWIDпрежнему требуется, чтобы не возвращать только строки, которые появляются первыми на странице данных.

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

Мартин Смит
источник
Я нашел это на веб-сайте Microsoft: TABLESAMPLE можно использовать для быстрого возврата выборки из большой таблицы, когда выполняется одно из следующих условий: выборка не обязательно должна быть действительно случайной выборкой на уровне отдельных строк. Строки на отдельных страницах таблицы не соотносятся с другими строками на той же странице.
Марк Энтинг,
1
@MarkEntingh - в этом случае TOP 1не имеет значения, коррелированы ли строки на одной странице или нет. Вы выбираете только один из них.
Мартин Смит
9

Также попробуйте свой метод, чтобы получить случайный идентификатор между MIN (Id) и MAX (Id), а затем

SELECT TOP 1 * FROM table WHERE Id >= @yourrandomid

Это всегда даст вам одну строку.

Скливвз
источник
2
-1, это будет работать, только если между min и max нет отсутствующих идентификаторов. Если один будет удален, то тот же идентификатор будет сгенерирован случайной функцией, вы получите нулевые записи обратно.
Neil N
6
@Neil, не совсем - он предоставит вам первую строку с идентификатором, превышающим случайное число, если идентификаторы отсутствуют. Проблема здесь в том, что вероятность выхода каждой строки непостоянна. Но опять же в большинстве случаев этого достаточно.
Sklivvz
1
+1. Для модульного тестирования, которое должно достигать разных значений, этого достаточно - если вы запрашиваете настоящий случайный результат, тогда это что-то еще. Но в контексте OP этого должно быть достаточно.
TomTom
7

Если вы хотите выбрать большие данные, лучший способ, который я знаю:

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

Источник: MSDN

хмфаримани
источник
Я не уверен, но думаю, что использование RAND (), а не NEWID () для генерации действительно случайных чисел может быть лучше из-за недостатков использования NEWID () в процессе выбора.
QMaster
Я пытаюсь использовать этот метод с точным количеством записей, а не с процентной базой, я сделал это с расширением диапазона выбора и ограничением с помощью TOP n, есть ли какие-либо предложения?
QMaster
Я обнаружил еще одну проблему с этим сценарием. Если вы используете group by, вы всегда будете получать один и тот же порядок случайно выбранных строк, поэтому кажется, что в небольших таблицах подход @skilvvz является наиболее правильным.
QMaster
0

Я искал способы улучшить методы, которые пробовал, и наткнулся на этот пост. Я понимаю, что он старый, но этого метода нет в списке. Я создаю и применяю тестовые данные; здесь показан метод «адреса» в SP, вызываемом с помощью @st (состояние с двумя символами)

Create Table ##TmpAddress (id Int Identity(1,1), street VarChar(50), city VarChar(50), st VarChar(2), zip VarChar(5))
Insert Into ##TmpAddress(street, city, st, zip)
Select street, city, st, zip 
From tbl_Address (NOLOCK)
Where st = @st


-- unseeded RAND() will return the same number when called in rapid succession so
-- here, I seed it with a guaranteed different number each time. @@ROWCOUNT is the count from the most recent table operation.

Set @csr = Ceiling(RAND(convert(varbinary, newid())) * @@ROWCOUNT)

Select street, city, st, Right(('00000' + ltrim(zip)),5) As zip
From ##tmpAddress (NOLOCK)
Where id = @csr
user2788934
источник
0

Если вам действительно нужна случайная выборка отдельных строк, измените свой запрос, чтобы отфильтровать строки случайным образом, вместо использования 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. "

Источник: http://technet.microsoft.com/en-us/library/ms189108(v=sql.105).aspx

Это дополнительно объясняется ниже:

Как это работает? Давайте выделим предложение WHERE и объясним его.

Функция КОНТРОЛЬНАЯ СУММА вычисляет контрольную сумму по элементам в списке. Спорный вопрос о том, требуется ли вообще SalesOrderID, поскольку NEWID () - это функция, возвращающая новый случайный GUID, поэтому умножение случайного числа на константу в любом случае должно приводить к случайному результату. Действительно, исключение SalesOrderID, похоже, не имеет значения. Если вы увлеченный статистик и можете оправдать включение этого, пожалуйста, используйте раздел комментариев ниже и дайте мне знать, почему я ошибаюсь!

Функция КОНТРОЛЬНАЯ СУММА возвращает ПЕРЕМЕННУЮ. Выполнение побитовой операции И с 0x7fffffff, что эквивалентно (111111111 ...) в двоичном формате, дает десятичное значение, которое фактически является представлением случайной строки из нулей и единиц. Деление на коэффициент 0x7fffffff эффективно нормализует это десятичное число до числа от 0 до 1. Затем, чтобы решить, заслуживает ли каждая строка включения в окончательный набор результатов, используется порог 1 / x (в данном случае 0,01), где x - процент данных, извлекаемых в качестве выборки.

Источник: https://www.mssqltips.com/sqlservertip/3157/different-ways-to-get-random-data-for-sql-server-data-sampling

XpiritO
источник