Как сгенерировать случайное число для каждой строки в TSQL Select?


328

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

SELECT table_name, RAND() magic_number 
FROM information_schema.tables 

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

Это для Microsoft SQL Server 2000.


4
Есть ли решение, которое не использует NEWID ()? Я хочу иметь возможность генерировать ту же последовательность случайных чисел для данного семени.
Рори Маклауд

@Rory Задайте это как новый вопрос, это привлечет больше внимания. (Мой ответ будет состоять в том, чтобы использовать фиксированные таблицы случайных чисел, например. Например, этот знаменитый стандартный набор случайных чисел: rand.org/pubs/monograph_reports/MR1418/index.html )
MatthewMartin,

2
Посмотрите @ RAND (Transact-SQL)
AminM

RAND был введен в 2005 году, этот вопрос был задан в 2009 году, в каких организациях все еще использовался SQL 2000, потому что это была первая версия, достаточно хорошая, чтобы использовать ее вечно.
MatthewMartin

Рори МакЛауд спросил: «Есть ли решение для этого, которое не использует NEWID ()? Я хочу иметь возможность генерировать такую ​​же последовательность случайных чисел для данного семени». Ответ - да, но немного запутанный. 1. Создайте представление, которое возвращает select rand () 2. Создайте пользовательскую функцию, которая выбирает значение из представления. 3. Прежде чем выбрать ваши данные, запустите функцию rand (). 4. Используйте UDF в вашем операторе выбора. Я
выложу

Ответы:


516

Взгляните на 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, потому что СУММА () в два раза больше шансов для их создания . Это проще продемонстрировать визуально. Ниже представлен весь возможный набор результатов для нашего воображаемого целочисленного диапазона:

Целевая контрольная сумма: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
Диапазон Результат: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 0 1 2 3 4 5

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


28
На этой связанной странице было решение: ABS (CHECKSUM (NewId ()))% 14
MatthewMartin

7
% 14 будет возвращать числа от 0 до 13
CoderDennis

7
@ Денис Палмер, просто добавь 1
КМ.

59
Мы только что обнаружили гениальную ошибку с этим. Поскольку контрольная сумма возвращает целое число, а диапазон целого числа от -2 ^ 31 (-2 147 483 648) до 2 ^ 31-1 (2 147 483 647), функция abs () может вернуть ошибку переполнения, если результат окажется точно равным -2 147 483 648. ! Шансы, очевидно, очень малы, около 1 на 4 миллиарда, однако мы каждый день запускали их по таблице строк ~ 1,8 млрд, так что это происходило примерно раз в неделю! Исправление - привести контрольную сумму к bigint перед прессом.
EvilPuppetMaster

17
Я думаю, что это должно сказать «равномерное распределение», а не «нормализованное распределение» - каждое число одинаково вероятно, это не кривая колокола. «Нормализованный» имеет конкретное математическое значение.
AnotherParker

95

При вызове несколько раз в одном пакете 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.


Как вы получаете номер из guid или varbinary? Я обновлю вопрос, чтобы указать, что я надеюсь на целое число.
МэтьюМартен

1
Вы умножаете это на число и напишите это :), так что если вы хотите пять цифр, умножьте на 100000 и преобразуйте в int. Уродливо, но достаточно просто сделать.
Джереми Смит

1
В качестве дальнейшего дополнения - которое даст вам до пяти цифр - если вы хотите заполнить его нулями, вам придется использовать тип данных char и использовать replicate для заполнения нулями до 5 цифр.
Джереми Смит

Если вы используете функцию потолка вместо пола, вам не нужно добавлять 1.
PopeDarren

Даже когда я использую это, иногда RAND () всегда дает мне один и тот же результат. Даже более странно, что иногда он переходит от правильного к неправильному поведению в зависимости от того, сколько раз я его использую. Я пытаюсь реализовать СЛУЧАЙНОЕ ВНУТРЕННЕЕ СОЕДИНЕНИЕ, и если я запрашиваю более 19 (!!!) строк, он начинает давать мне всегда один и тот же результат ...
Йоханнес Венту

72
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

39

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

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

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


Верхняя граница является исключительной для этого метода, поэтому, если вы хотите включить верхний номер, вам нужно будет это сделатьFLOOR(RAND(CHECKSUM(NEWID()))*(10000-1000)+1000)
vaindil

20

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

В 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

13

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

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

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


5

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

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

FLOOR( RAND(<yourseed>) * 14) + 1

Это работает в теории, но на практике я обнаружил, что RAND(<seed>)не кажется слишком случайным для незначительных изменений в <seed>. Например, быстрый тест, который я сделал: я позволил <seed>184380, 184383, 184386, и соответствующие RAND(<seed>)значения были: 0,14912, 0,14917, 0,14923.
ImaginaryHuman072889

Может быть, чтобы получить более «кажущиеся» случайные результаты, попробуйте что-то вроде:RAND(<seed>)*100000) - FLOOR(RAND(<seed>)*100000)
ImaginaryHuman072889

5

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

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

4

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


Простейшее! Хотя значения кажутся намного более рассеяны, используя цифры от середины , что, как RIGHT(CONVERT(BIGINT, RAND(RecNo) * 1000000000000), 2) (примечание: я вижу RIGHTнеявно преобразовать BIGINTв CHAR, но быть строгим, вы бы другая CONVERTтам).
Doug_Ivison

4

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

SELECT table_name, newid() magic_number 
FROM information_schema.tables

4

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

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


Мертвая ссылка :( Есть какие-нибудь копии, которые можно было бы включить в ответ?
jocull

Он помещает RAND()в представление, помещает его SELECTв функцию, а затем вызывает функцию из любого места. Умная.
Doug_Ivison

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

4
select round(rand(checksum(newid()))*(10)+20,2)

Здесь случайное число будет находиться в диапазоне от 20 до 30. Это roundдаст максимум два знака после запятой.

Если вы хотите отрицательные числа, вы можете сделать это с

select round(rand(checksum(newid()))*(10)-60,2)

Тогда минимальное значение будет -60, а максимальное - -50.


3

Это так же просто, как:

DECLARE @rv FLOAT;
SELECT @rv = rand();

И это поместит в таблицу случайное число от 0 до 99:

CREATE TABLE R
(
    Number int
)

DECLARE @rv FLOAT;
SELECT @rv = rand();

INSERT INTO dbo.R
(Number)
    values((@rv * 100));

SELECT * FROM R

2

Проблема, с которой я иногда сталкиваюсь с выбранным «Ответом», заключается в том, что распределение не всегда равномерное. Если вам нужно очень равномерное распределение случайных чисел от 1 до 14 среди большого количества строк, вы можете сделать что-то вроде этого (моя база данных имеет 511 таблиц, так что это работает. Если у вас меньше строк, чем у диапазона случайных чисел, это не работает хорошо):

SELECT table_name, ntile(14) over(order by newId()) randomNumber 
FROM information_schema.tables

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

Помните, у меня есть 511 таблиц в моей базе данных (что касается только b / c, который мы выбираем из information_schema). Если я беру предыдущий запрос и помещаю его во временную таблицу #X, а затем запускаю этот запрос для полученных данных:

select randomNumber, count(*) ct from #X
group by randomNumber

Я получаю этот результат, показывая, что мое случайное число ОЧЕНЬ равномерно распределено по множеству строк:

введите описание изображения здесь


2
select ABS(CAST(CAST(NEWID() AS VARBINARY) AS INT)) as [Randomizer]

всегда работал на меня



1
    DROP VIEW IF EXISTS vwGetNewNumber;
    GO
    Create View vwGetNewNumber
    as
    Select CAST(RAND(CHECKSUM(NEWID())) * 62 as INT) + 1 as NextID,
    'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'as alpha_num;

    ---------------CTDE_GENERATE_PUBLIC_KEY -----------------
    DROP FUNCTION IF EXISTS CTDE_GENERATE_PUBLIC_KEY;  
    GO
    create function CTDE_GENERATE_PUBLIC_KEY()
    RETURNS NVARCHAR(32)
    AS 
    BEGIN
        DECLARE @private_key NVARCHAR(32);
        set @private_key = dbo.CTDE_GENERATE_32_BIT_KEY();
        return @private_key;
    END;
    go

---------------CTDE_GENERATE_32_BIT_KEY -----------------
DROP FUNCTION IF EXISTS CTDE_GENERATE_32_BIT_KEY;  
GO
CREATE function CTDE_GENERATE_32_BIT_KEY()
RETURNS NVARCHAR(32)
AS 
BEGIN
    DECLARE @public_key NVARCHAR(32);
    DECLARE @alpha_num NVARCHAR(62);
    DECLARE @start_index INT = 0;
    DECLARE @i INT = 0;
    select top 1 @alpha_num = alpha_num from vwGetNewNumber;
        WHILE @i < 32
        BEGIN
          select top 1 @start_index = NextID from vwGetNewNumber;
          set @public_key = concat (substring(@alpha_num,@start_index,1),@public_key);
          set @i = @i + 1;
        END;
    return @public_key;
END;
    select dbo.CTDE_GENERATE_PUBLIC_KEY() public_key;

извините @arnt, если я не очень хорошо объяснил,
ичак хури

извините, @arnt, у нас здесь есть две функции CTDE_GENERATE_32_BIT_KEY, которые генерируют 32-битный буквенно-цифровой ключ (может быть увеличен до большего или меньшего), а другая называется CTDE_GENERATE_PUBLIC_KEY, которая вызывает первую функцию и возвращает открытый ключ из 32 бит, или вы можете вернуть закрытый ключ 16 бит ... вам просто нужно вызвать select dbo.CTDE_GENERATE_PUBLIC_KEY () в качестве открытого ключа; логика заключается в том, что мы выбираем один символ из списка буквенно-цифровых символов 32 раза и объединяем их вместе, чтобы получить случайный буквенно-цифровой ключ. после исследования.
Ичак Хури

Ницца. Это объяснение делает его намного лучшим ответом. (Кто-то пометил его для удаления; я проголосовал за то, чтобы оставить его открытым и оставил этот комментарий для вас.)
arnt

0

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

SELECT RAND(convert(varbinary, newid()))*(b-a)+a magic_number 

Где aнижнее число и bверхнее число


1
Можете ли вы попытаться быть более ясным, отвечая на вопрос?
Юнус Темурленк

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.