如何为TSQL Select中的每一行生成随机数?


328

我表中的每一行都需要一个不同的随机数。以下看似显而易见的代码为每行使用相同的随机值。

SELECT table_name, RAND() magic_number 
FROM information_schema.tables 

我想从中获取整数或浮点数。故事的其余部分是我将使用该随机数创建一个相对于已知日期的随机日期偏移量,例如,相对于开始日期偏移1-14天。

这是用于Microsoft SQL Server 2000。


4
有没有不使用NEWID()的解决方案吗?我希望能够为给定的种子生成相同的随机数序列。
罗里·麦克劳德

@Rory询问作为新问题,它将得到更多关注。(我的答案是使用固定的随机数表,例如。例如,这个著名的随机数标准集:rand.org/pubs/monograph_reports/MR1418/index.html
MatthewMartin 2010年


RAND于2005年引入,2009年提出了这个问题,哪些组织仍在使用SQL 2000,因为它是第一个足以永久使用的版本。
MatthewMartin 2014年

Rory MacLeod问:“是否有不使用NEWID()的解决方案?我希望能够为给定种子生成相同的随机数序列。” 答案是肯定的,但是有点令人费解。1.创建一个返回select rand()的视图。2.创建一个从视图中选择值的UDF。3.在选择数据之前,请为rand()函数设置种子。4.在您的select语句中使用UDF。我会后下面一个完整的例子
Mitselplik

Answers:


516

看一下SQL Server-基于集合的随机数,其中有非常详细的说明。

总而言之,以下代码生成一个介于0到13之间(含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 2009年

7
%14将返回0到13之间的数字
CoderDennis

7
@Dennis Palmer,只需添加1
KM。

59
我们刚刚发现了一个天才的错误。因为校验和返回一个int,并且int的范围是-2 ^ 31(-2,147,483,648)到2 ^ 31-1(2,147,483,647),所以abs()函数可以返回溢出错误(如果结果恰好是-2,147,483,648) !机会显然很低,大约为40亿分之一,但是我们每天在〜1.8b的行表上运行它,所以它大约每周发生一次!解决的方法是在abs之前将校验和转换为bigint。
EvilPuppetMaster

17
我认为应该说的是“均匀分布”而不是“归一化分布”,每个数字都具有同等的可能性,而不是钟形曲线。“归一化”具有特定的数学含义。
AnotherParker

95

在一个批次中多次调用时,rand()返回相同的数字。

我建议使用convert(varbinarynewid())作为种子参数:

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

newid() 保证每次调用都返回一个不同的值,即使在同一批处理中也是如此,因此将其用作种子将提示rand()每次给出不同的值。

编辑以获取1到14之间的随机整数。


您如何从GUID或VARBINARY中获得一个数字?我将更新问题以表明我希望输入一个整数。
MatthewMartin

1
您可以将其乘以数字并将其底数:)乘以100,然后转换为整数。丑陋,但足够简单。
杰里米·史密斯

1
作为进一步的附录-您最多可以输入五位数字-如果要对其进行零填充,则必须使用char数据类型,并使用复制将零位填充至最多5位数字。
杰里米·史密斯

如果使用吊顶功能,而不是地板上,你不必加1
PopeDarren

即使使用此方法,有时RAND()总是给我相同的结果。甚至更陌生,根据我使用它的次数,有时它会从正确的行为跳到错误的行为。我正在尝试实现一个RANNER INNER JOIN,并且如果我要求超过19(!!!)行,它将开始总是给我相同的结果...
Johannes Wentu,

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/zh-CN/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/zh-CN/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 '18

20

回答旧问题,但是以前没有提供过此答案,希望这对通过搜索引擎找到此结果的人很有用。

在SQL Server 2008中,引入了一个新函数,该函数CRYPT_GEN_RANDOM(8)使用CryptoAPI生成密码强的随机数,返回为VARBINARY(8000)。这是文档页面: https //docs.microsoft.com/zh-cn/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

如果在表SELECT查询中使用,Rand()函数将生成相同的随机数。如果对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()函数设置种子,然后在select语句中使用UDF。

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隐式地将BIGINTto 转换为CHAR,但要严格一点,您还要在其中添加另一个CONVERT)。
Doug_Ivison

4

如果您不需要整数,而是任何随机唯一标识符,则可以使用 newid()

SELECT table_name, newid() magic_number 
FROM information_schema.tables

4

无效

他放入RAND()一个视图,SELECT将该视图的一个放入函数中,然后从任何地方调用该函数。聪明。
Doug_Ivison 2015年

我发布了一种解决方案,该解决方案与链接文章中的解决方案完全相同,但是在此博客中,五个帖子之前直接作为答案!没有人称我聪明的嫉妒面孔呵呵
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个表(仅与我们从information_schema中选择的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位的私钥...您只需要调用select dbo.CTDE_GENERATE_PUBLIC_KEY()作为公钥;背后的逻辑是,我们从字母数字字符列表中选择32个字符,然后将它们串联在一起,以获得随机的字母数字键。经过研究。
ichak khoury

真好 这种解释使其成为更好的答案。(有人将其标记为删除;我投票决定将其保留为开放状态,并为您留下评论。)
arnt

0

尝试这个:

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

哪里a是较低的号码,b是上数


1
您能否在回答问题时变得更加清晰?
尤努斯·特穆伦克

0
Update my_table set my_field = CEILING((RAND(CAST(NEWID() AS varbinary)) * 10))

1到10之间的数字。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.