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

328

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

SELECT table_name, RAND() magic_number 
FROM information_schema.tables 

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

Это для Microsoft SQL Server 2000.

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

SQLMenace
источник
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 для предыдущего примера)

Vova
источник
Верхняя граница является исключительной для этого метода, поэтому, если вы хотите включить верхний номер, вам нужно будет это сделать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]

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

MicSim
источник
5

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

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

FLOOR( RAND(<yourseed>) * 14) + 1
CoderDennis
источник
Это работает в теории, но на практике я обнаружил, что 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
Mitselplik
источник
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.

Tirthankar
источник
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
Rosjier Hall
источник
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]

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

theteague
источник
2

Используйте newid ()

select newid()

или возможно это

select binary_checksum(newid())
Крис Клепейс
источник
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верхнее число

Rutendo
источник
1
Можете ли вы попытаться быть более ясным, отвечая на вопрос?
Юнус Темурленк
0
Update my_table set my_field = CEILING((RAND(CAST(NEWID() AS varbinary)) * 10))

Число от 1 до 10.

user3478586
источник