SqlServer Rand () вопрос - PullRequest
       7

SqlServer Rand () вопрос

1 голос
/ 01 ноября 2009

Я пишу процедуру, в которой для каждого вызова необходимо получить одно случайное число. Эта процедура вызывается из нашего веб-сервиса .net.

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

Похоже, что rand () повторяется при каждом вызове хранимой процедуры SqlServer. Из того, что я понимаю, это проблема, потому что нужно запустить генератор случайных чисел один раз, и он не получит хорошую последовательность псевдослучайных чисел, если каждый повторяет каждый вызов rand. Кроме того, кажется, что вызовы к тому же sp, которые находятся в пределах 1 или 2 миллисекунд, засеваются с тем же значением.

Вот сама инструкция в хранимой процедуре.

DECLARE @randomNumber char(9)

SET @randomNumber = RIGHT('00000' + CAST(CAST(rand()*100000 AS INT) AS VARCHAR(5)),5)
+ RIGHT('00000' + CAST(CAST(rand()*10000 AS INT) AS VARCHAR(4)),4)

У кого-нибудь есть предложения по исправлению?

Должен ли я написать свой собственный генератор случайных чисел, который будет посеян один раз и сохранит свое состояние в таблице между вызовами? Как SQL Server затравливает rand ()? Это действительно случайно или если вы вызываете sp в течение 1 или 2 миллисекунд друг от друга на отдельных соединениях, будет ли он засеян одним и тем же семенем, вызывая столкновение?

Ответы [ 4 ]

9 голосов
/ 01 ноября 2009

Если вы используете SQL Server 2008, вы можете использовать функцию CRYPT_GEN_RANDOM (). Это будет рандомизировать данные для каждой строки, даже если вы пытались вычислить миллионы случайных чисел в одном выполнении запроса и не имели проблем с заполнением:

SELECT CAST(RIGHT(CAST(CAST(CRYPT_GEN_RANDOM(1) AS INT) AS VARCHAR(100)), 1) AS INT)

Вот ссылка на статью BOL:

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

5 голосов
/ 01 ноября 2009

В вашем примере замените rand()*10000 на ABS(CHECKSUM(NEWID())) % 9999

Однако для символа (9):

SELECT RIGHT('000000000' + CAST(ABS(CHECKSUM(NEWID()) % 999999999) AS char(9), 9)

Для посева RAND в случайном порядке ...

RAND(CHECKSUM(NEWID()))

Edit:

Обратите внимание, RAND плохо реализован в SQL Server. Не используйте его.

0 голосов
/ 01 ноября 2009

Вы можете использовать таблицу только с полем идентификатора для создания уникальных nunbers для использования в качестве семени:

declare
  @randomNumber char(9),
  @seed1 int,
  @seed2 int

insert into SeedTable () values ()
set @seed1 = scope_identity()

insert into SeedTable () values ()
set @seed2 = scope_identity()

set @randomNumber = right('00000' + 
    cast(cast(rand(@seed1) * 100000 as int) as varchar(5)), 5) +
    right('00000' + 
    cast(cast(rand(@seed2) * 10000 as int) as varchar(4)), 4)

if (@seed2 > 10000) truncate table SeedTable
0 голосов
/ 01 ноября 2009

Функция RAND () имеет необязательный параметр seed, который вы можете использовать для этого. Если вы передадите последнее сгенерированное случайное значение в качестве начального числа следующему вызову rand (), вы гарантированно получите новое случайное число.

Спасибо gbn за указание на то, что семя является целым числом, а rand () возвращает число с плавающей запятой. С этим знанием, вот рабочий пример! Сначала создайте таблицу:

create table RandomNumber (number float)
insert into RandomNumber values (rand())

Затем возьмите случайное число и сохраните новый номер в транзакции:

declare @new float
begin transaction
select @new = rand(-2147483648 + 4294967295 * number)
    from RandomNumber with (updlock, holdlock)
update RandomNumber set number = @new
commit transaction
print 'Next bingo number is: ' + cast(cast(@new*100 as int) as varchar)

Целочисленное значение SQL Server варьируется от -2147483648 до 2147483647, а случайное число - это число с плавающей точкой от 0,0 до 1,0. Так что -2147483648 + 4294967295 * number должно охватывать весь диапазон доступных целых чисел.

Транзакция гарантирует, что только одно соединение за раз считывает и сохраняет новый номер. Таким образом, числа являются случайными даже при разных подключениях к SQL Server. (Кстати, я голосовал за ответ ГБН, кажется, намного проще.)

...