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


328

Мені потрібно різне випадкове число для кожного рядка моєї таблиці. Наступний, здавалося б, очевидний код використовує однакове випадкове значення для кожного рядка.

SELECT table_name, RAND() magic_number 
FROM information_schema.tables 

Я хотів би отримати INT або FLOAT з цього. Інша частина історії полягає в тому, що я буду використовувати це випадкове число, щоб створити випадкове зміщення дати від відомої дати, наприклад, зсув 1-14 днів від дати початку.

Це для Microsoft SQL Server 2000.


4
Чи є для цього рішення, яке не використовує NEWID ()? Я хочу мати можливість генерувати ту саму послідовність випадкових чисел для даного насіння.
Rory MacLeod

@ Рорі Задайте це питання як нове запитання, воно приверне більше уваги. (Моєю відповіддю було б використання фіксованих таблиць випадкових чисел, наприклад. Наприклад, цей відомий стандартний набір випадкових чисел: 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. Створіть UDF, який вибере значення з представлення. 3. Перш ніж вибирати свої дані, вставте функцію rand (). 4. Використовуйте UDF у своєму операторі select. Я опублікую повний приклад нижче
Mitselplik

Відповіді:


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, оскільки CHECKSUM () вдвічі частіше їх генерує. Це простіше продемонструвати візуально. Нижче наведено весь можливий набір результатів для нашого уявного цілого діапазону:

Цінна сума: 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
Ми щойно виявили геніальну помилку з цим. Оскільки контрольна сума повертає int, а діапазон int становить від -2 ^ 31 (-2,147,483,648) до 2 ^ 31-1 (2,147,483,647), функція abs () може повернути помилку переповнення, якщо результат трапиться точно -2,147,483,648 ! Шанси, очевидно, дуже низькі, приблизно 1 на 4 мільярди, проте ми працювали над таблицею ~ 1,8b рядок щодня, тому це відбувалося приблизно раз на тиждень! Виправлення полягає в тому, щоб кинути контрольну суму на bigint перед абс.
EvilPuppetMaster

17
Я думаю, що це повинно сказати "рівномірний розподіл", а не "нормалізований розподіл" - кожне число однаково вірогідне, це не крива дзвона. "Нормалізований" має специфічне математичне значення.
Ще один паркер

95

Коли викликається кілька разів однією партією, rand () повертає те саме число.

Я б запропонував використовувати convert ( varbinary, newid()) як початковий аргумент:

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

newid() гарантовано повертає інше значення кожного разу, коли воно викликається, навіть у межах однієї партії, тому використання його як насінного файлу запропонує rand () надавати інше значення кожного разу.

Відредаговано, щоб отримати випадкове ціле число від 1 до 14.


Як дістати номер із настанови чи варбінера? Я оновлю питання, щоб вказати, що сподіваюся на ціле число.
MatthewMartin

1
Ви помножуєте його на число і повертаєте його :), тому якщо ви хочете п'ять цифр, помножте на 100000 і перетворите на int. Некрасивий, але досить простий.
Джеремі Сміт

1
Як додатковий додаток - це дасть вам до п’яти цифр - якщо ви хочете, щоб його замінили на нульовому рівні, вам доведеться використовувати тип даних char та використовувати репліку на нульову панель до 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

Я опублікував рішення, яке вирішує проблему точно так само, як у зв'язаній статті, але ось у цьому блозі безпосередньо як відповідь п’ять повідомлень тому! Ніхто не називав мене розумною заздрістю обличчям hehe
Mitselplik

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, яке ми вибираємо з інформаційної схеми). Якщо я беру попередній запит і вкладаю його в тимчасову таблицю #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, якщо я не пояснив добре,
ichak khoury

Вибачте @arnt, у нас є дві функції CTDE_GENERATE_32_BIT_KEY, яка генерує 32-бітний буквено-цифровий ключ (може бути розширений, щоб бути більш-менш), а інша називається CTDE_GENERATE_PUBLIC_KEY, яка викликає першу функцію і повертає назад відкритий ключ 32-бітного або ви можете повернутися приватний ключ 16-бітний ... просто потрібно зателефонувати виберіть dbo.CTDE_GENERATE_PUBLIC_KEY () як відкритий ключ; логіка полягає в тому, що ми вибираємо один символ зі списку буквено-цифрових символів 32 рази і об'єднуємо їх разом, щоб отримати випадковий буквено-цифровий ключ. після досліджень.
ichak khoury

Приємно. Це пояснення дає набагато кращу відповідь. (Хтось позначив це на видалення; я проголосував за те, щоб залишити його відкритим, і залишив цей коментар для вас.)
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.