Как сгенерировать случайное число для каждой строки в TSQL Select? - PullRequest
291 голосов
/ 25 июня 2009

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

SELECT table_name, RAND() magic_number 
FROM information_schema.tables 

Я бы хотел получить INT или FLOAT из этого. В остальной части истории я собираюсь использовать это случайное число для создания случайного смещения даты относительно известной даты, например Смещение на 1-14 дней от даты начала.

Это для Microsoft SQL Server 2000.

Ответы [ 18 ]

455 голосов
/ 25 июня 2009

Взгляните на SQL Server - установите случайные числа на основе , в которых есть очень подробное объяснение.

Чтобы подвести итог, следующий код генерирует случайное число от 0 до 13 включительно с нормализованным распределением:

ABS(CHECKSUM(NewId())) % 14

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

Небольшое предупреждение для математических орехов в комнате: в этом коде есть очень небольшое смещение. CHECKSUM() приводит к числам, которые являются одинаковыми по всему диапазону типа данных sql Int, или, по крайней мере, настолько близкими, насколько может показать мое (редакторское) тестирование. Однако будет некоторое смещение, когда CHECKSUM () выдаст число в самом верхнем конце этого диапазона. Каждый раз, когда вы получаете число между максимально возможным целым числом и последним точным кратным размера желаемого диапазона (в данном случае 14) перед этим максимальным целым числом, эти результаты предпочтительнее, чем оставшаяся часть вашего диапазона, которая не может быть получена из это последнее кратное 14.

В качестве примера представьте, что весь диапазон типа Int равен только 19. 19 - это наибольшее возможное целое число, которое вы можете удерживать. Когда CHECKSUM () приводит к 14-19, они соответствуют результатам 0-5. Эти числа будут сильно предпочтительнее 6-13, потому что CHECKSUM () в два раза чаще генерирует их. Это проще продемонстрировать визуально. Ниже представлен весь возможный набор результатов для нашего воображаемого целочисленного диапазона:

Checksum Integer: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
Range Result:     0 1 2 3 4 5 6 7 8 9 10 11 12 13  0  1  2  3  4  5

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

87 голосов
/ 25 июня 2009

При вызове несколько раз в одном пакете rand () возвращает одно и то же число.

Я бы предложил использовать convert (varbinary, newid()) в качестве аргумента seed:

SELECT table_name, 1.0 + floor(14 * RAND(convert(varbinary, newid()))) magic_number 
FROM information_schema.tables

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

Отредактировано для получения случайного целого числа от 1 до 14.

65 голосов
/ 28 января 2012
RAND(CHECKSUM(NEWID()))

Выше будет сгенерировано (псевдо) случайное число от 0 до 1, исключая. Если используется в выборе, поскольку начальное значение изменяется для каждой строки, оно генерирует новое случайное число для каждой строки (однако не гарантируется, что будет генерироваться уникальное число для каждой строки).

Пример в сочетании с верхним пределом 10 (производит числа 1 - 10):

CAST(RAND(CHECKSUM(NEWID())) * 10 as INT) + 1

Документация по Transact-SQL:

  1. CAST(): https://docs.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql
  2. RAND(): http://msdn.microsoft.com/en-us/library/ms177610.aspx
  3. CHECKSUM(): http://msdn.microsoft.com/en-us/library/ms189788.aspx
  4. NEWID(): https://docs.microsoft.com/en-us/sql/t-sql/functions/newid-transact-sql
34 голосов
/ 05 августа 2015

Генерация случайных чисел от 1000 до 9999 включительно:

FLOOR(RAND(CHECKSUM(NEWID()))*(9999-1000+1)+1000)

"+ 1" - включить значения верхней границы (9999 для предыдущего примера)

17 голосов
/ 14 июня 2017

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

В SQL Server 2008 была введена новая функция CRYPT_GEN_RANDOM(8), которая использует CryptoAPI для получения криптографически сильного случайного числа, возвращаемого как VARBINARY(8000). Вот страница документации: https://docs.microsoft.com/en-us/sql/t-sql/functions/crypt-gen-random-transact-sql

Таким образом, чтобы получить случайное число, вы можете просто вызвать функцию и привести ее к нужному типу:

select CAST(CRYPT_GEN_RANDOM(8) AS bigint)

или чтобы получить float между -1 и +1, вы можете сделать что-то вроде этого:

select CAST(CRYPT_GEN_RANDOM(8) AS bigint) % 1000000000 / 1000000000.0
11 голосов
/ 25 июня 2009

Функция Rand () сгенерирует то же случайное число, если оно используется в запросе SELECT таблицы. То же самое относится, если вы используете семя для функции Rand. Альтернативный способ сделать это, используя это:

SELECT ABS(CAST(CAST(NEWID() AS VARBINARY) AS INT)) AS [RandomNumber]

Получил информацию от здесь , которая очень хорошо объясняет проблему.

5 голосов
/ 11 мая 2015

Если вам нужно сохранить начальное число, чтобы оно каждый раз генерировало «одни и те же» случайные данные, вы можете сделать следующее:

1. Создать представление, которое возвращает select rand ()

if object_id('cr_sample_randView') is not null
begin
    drop view cr_sample_randView
end
go

create view cr_sample_randView
as
select rand() as random_number
go

2. Создайте UDF, который выбирает значение из представления.

if object_id('cr_sample_fnPerRowRand') is not null
begin
    drop function cr_sample_fnPerRowRand
end
go

create function cr_sample_fnPerRowRand()
returns float
as
begin
    declare @returnValue float
    select @returnValue = random_number from cr_sample_randView
    return @returnValue
end
go

3. Прежде чем выбирать ваши данные, запустите функцию rand (), а затем используйте UDF в вашем операторе select.

select rand(200);   -- see the rand() function
with cte(id) as
(select row_number() over(order by object_id) from sys.all_objects)
select 
    id,
    dbo.cr_sample_fnPerRowRand()
from cte
where id <= 1000    -- limit the results to 1000 random numbers
5 голосов
/ 25 июня 2009

У вас есть целочисленное значение в каждой строке, которое вы можете передать в качестве начального числа в функцию RAND?

Чтобы получить целое число от 1 до 14, я думаю, это сработает:

FLOOR( RAND(<yourseed>) * 14) + 1
4 голосов
/ 25 июня 2009

Вам нужно будет вызвать RAND () для каждой строки. Вот хороший пример

https://web.archive.org/web/20090216200320/http://dotnet.org.za/calmyourself/archive/2007/04/13/sql-rand-trap-same-value-per-row.aspx

4 голосов
/ 25 июня 2009

Если вам не нужно, чтобы это было целое число, но любой произвольный уникальный идентификатор, вы можете использовать newid()

SELECT table_name, newid() magic_number 
FROM information_schema.tables
...