如何为 T-SQL 选择中的每一行生成一个随机数?

Posted

技术标签:

【中文标题】如何为 T-SQL 选择中的每一行生成一个随机数?【英文标题】:How do I generate a random number for each row in a T-SQL select? 【发布时间】:2010-11-05 22:03:32 【问题描述】:

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

SELECT table_name, RAND() magic_number 
FROM information_schema.tables 

我想从中得到一个 INT 或 FLOAT。剩下的故事是我将使用这个随机数来创建一个与已知日期的随机日期偏移量,例如从开始日期偏移 1-14 天。

这适用于 Microsoft SQL Server 2000。

【问题讨论】:

是否有不使用 NEWID() 的解决方案?我希望能够为给定的种子生成相同的随机数序列。 @Rory 问这个作为新问题,它会得到更多的关注。 (我的回答是使用固定的随机数表,例如这个著名的随机数标准集:rand.org/pubs/monograph_reports/MR1418/index.html 看@RAND (Transact-SQL) RAND 是在 2005 年推出的,这个问题是在 2009 年提出的,哪些组织仍在使用 SQL 2000,因为那是第一个版本,足以永久使用。 Rory MacLeod 问道:“有没有不使用 NEWID() 的解决方案?我希望能够为给定的种子生成相同的随机数序列。”答案是肯定的,但它有点令人费解。 1. 创建一个返回 select rand() 的视图 2. 创建一个从视图中选择值的 UDF。 3. 在选择您的数据之前,先播种 rand() 函数。 4. 在您​​的选择语句中使用 UDF。我将在下面发布一个完整的示例 【参考方案1】:

看看SQL Server - Set based random numbers,里面有很详细的解释。

总而言之,以下代码生成一个介于 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 类型的实际范围要大得多...大到在大多数情况下几乎无法检测到偏差。但是,如果您发现自己为严重的安全代码执行此操作,请注意这一点。

【讨论】:

这个链接页面有解决方案:ABS(CHECKSUM(NewId())) % 14 % 14 将返回 0 到 13 之间的数字 @Dennis Palmer,加 1 我们刚刚发现了一个天才漏洞。因为校验和返回一个 int,并且 int 的范围是 -2^31 (-2,147,483,648) 到 2^31-1 (2,147,483,647),所以如果结果恰好是 -2,147,483,648,abs() 函数会返回溢出错误!机会显然非常低,大约 40 亿分之一,但是我们每天在 ~1.8b 行表上运行它,所以它大约每周发生一次!修复是在 abs 之前将校验和转换为 bigint。 我认为这应该说“均匀分布”而不是“归一化分布”——每个数字都有同样的可能性,它不是钟形曲线。“归一化”具有特定的数学含义。【参考方案2】:

当在一个批次中多次调用时,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 的随机整数。

【讨论】:

如何从 guid 或 varbinary 中获取数字?我将更新问题以表明我希望得到一个整数。 你将它乘以一个数字并取底:) 所以如果你想要五位数,乘以 100000,然后转换为一个整数。丑陋,但做起来很简单。 作为进一步的附录 - 这将为您提供最多五位数字 - 如果您想对其进行零填充,则必须使用 char 数据类型,并使用复制到零填充最多 5 位数。 如果使用天花板函数而不是地板,则不必加1。 即使我使用它,有时 RAND() 总是给我相同的结果。更奇怪的是,有时它会根据我使用它的次数从正确行为跳到错误行为。我正在尝试实现 RANDOM INNER JOIN,如果我要求超过 19 行(!!!),它开始给我总是相同的结果......【参考方案3】:
RAND(CHECKSUM(NEWID()))

上面将生成一个介于 0 和 1 之间的(伪)随机数,排他性。如果在 select 中使用,由于种子值每行都会发生变化,它会为每一行生成一个新的随机数(但不保证每行生成一个唯一的数字)。

与上限 10 组合时的示例(产生数字 1 - 10):

CAST(RAND(CHECKSUM(NEWID())) * 10 as INT) + 1

Transact-SQL 文档:

    CAST(): https://docs.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql RAND(): http://msdn.microsoft.com/en-us/library/ms177610.aspx CHECKSUM(): http://msdn.microsoft.com/en-us/library/ms189788.aspx NEWID(): https://docs.microsoft.com/en-us/sql/t-sql/functions/newid-transact-sql

【讨论】:

【参考方案4】:

1000 到 9999 之间的随机数生成:

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

"+1" - 包含上限值(上例为 9999)

【讨论】:

上限是这个方法独有的,所以如果你想包含最高数字你需要FLOOR(RAND(CHECKSUM(NEWID()))*(10000-1000)+1000)【参考方案5】:

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

在 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)

或者要获得介于 -1 和 +1 之间的 float,您可以执行以下操作:

select CAST(CRYPT_GEN_RANDOM(8) AS bigint) % 1000000000 / 1000000000.0

【讨论】:

实际上很好,当您担心 NEWID() 运行不足或语义不合适,或者只需要来自 ABS(CAST(CRYPT_GEN_RANDOM(4)AS INT)) 的正整数时。【参考方案6】:

如果在表 SELECT 查询中使用,Rand() 函数将生成相同的随机数。如果您对 Rand 函数使用种子,则同样适用。另一种方法是使用这个:

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

从here得到信息,很好的解释了问题。

【讨论】:

【参考方案7】:

每一行中是否有一个整数值可以作为种子传递给 RAND 函数?

要获得 1 到 14 之间的整数,我相信这会起作用:

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

【讨论】:

这在理论上可行,但在实践中我发现RAND(&lt;seed&gt;) 对于&lt;seed&gt; 的微小变化似乎不是很随机。例如我做的一个快速测试:我让&lt;seed&gt;为184380、184383、184386,对应的RAND(&lt;seed&gt;)值为:0.14912、0.14917、0.14923。 也许要获得更多“看似”的随机结果,尝试类似:RAND(&lt;seed&gt;)*100000) - FLOOR(RAND(&lt;seed&gt;)*100000)【参考方案8】:

如果您需要保留种子,以便每次生成“相同”的随机数据,您可以执行以下操作:

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 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

【讨论】:

【参考方案9】:

尝试在 RAND(seedInt) 中使用种子值。 RAND() 每条语句只会执行一次,这就是为什么你每次看到的数字都是一样的。

【讨论】:

最简单!尽管这些值看起来更加分散,但使用中间的数字,例如RIGHT(CONVERT(BIGINT, RAND(RecNo) * 1000000000000), 2)(注意:我看到RIGHT 隐式地将BIGINT 转换为CHAR,但严格来说,你会还有另一个CONVERT)。【参考方案10】:

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

SELECT table_name, newid() magic_number 
FROM information_schema.tables

【讨论】:

【参考方案11】:

您需要为每一行调用 RAND()。这是一个很好的例子

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

【讨论】:

死链接 :( 任何可以包含在答案中的副本? 他将RAND() 放入一个视图中,将该视图的SELECT 放入一个函数中,然后从任何地方调用该函数。聪明。 我发布了一个解决方案,解决问题的方式与链接文章中的完全相同,但在此博客中直接作为五个帖子前的答案!没人叫我聪明羡慕脸呵呵【参考方案12】:
select round(rand(checksum(newid()))*(10)+20,2)

这里的随机数将在 20 到 30 之间。 round 将给出最多两位小数。

如果你想要负数,你可以用

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

那么最小值将为-60,最大值为-50。

【讨论】:

【参考方案13】:

有时我在选择“答案”时遇到的问题是分布并不总是均匀的。如果您需要在许多行之间非常均匀地分布随机 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

我得到这个结果,表明我的随机数非常均匀地分布在许多行中:

【讨论】:

【参考方案14】:

很简单:

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

【讨论】:

【参考方案15】:
select ABS(CAST(CAST(NEWID() AS VARBINARY) AS INT)) as [Randomizer]

一直为我工作

【讨论】:

【参考方案16】:

使用newid()

select newid()

或者这个

select binary_checksum(newid())

【讨论】:

【参考方案17】:

如果你想生成一个介于 1 到 14 之间的随机数。

SELECT CONVERT(int, RAND() * (14 - 1) + 1)

SELECT ABS(CHECKSUM(NewId())) % (14 -1) + 1

【讨论】:

【参考方案18】:
    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,如果我解释得不好, sorry @arnt ,我们这里有两个函数 CTDE_GENERATE_32_BIT_KEY 生成一个 32 位字母数字密钥(可以扩展为更多或更少),另一个称为 CTDE_GENERATE_PUBLIC_KEY 调用第一个函数并返回 32 位公钥,或者您可以返回 16 位私钥……您只需调用 select dbo.CTDE_GENERATE_PUBLIC_KEY() 作为公钥; 背后的逻辑是我们从字母数字字符列表中选择一个字符 32 次并将它们连接在一起以获得随机的字母数字键。经过研究。 不错。这种解释使它成为一个更好的答案。 (有人将其标记为删除;我投票决定将其保持打开状态,并将该评论留给您。)【参考方案19】:
Update my_table set my_field = CEILING((RAND(CAST(NEWID() AS varbinary)) * 10))

1 到 10 之间的数字。

【讨论】:

【参考方案20】:

试试这个:

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

其中a 是小数,b 是大数

【讨论】:

你能在回答问题时尽量说得更清楚吗?【参考方案21】:

如果您需要特定数量的随机数,您可以使用递归 CTE:

;WITH A AS (
        SELECT 1 X, RAND() R
    UNION ALL
        SELECT X + 1, RAND(R*100000) --Change the seed
        FROM A
        WHERE X < 1000 --How many random numbers you need
    )
SELECT
    X
    , RAND_BETWEEN_1_AND_14 = FLOOR(R * 14 + 1)
FROM A
OPTION (MAXRECURSION 0) --If you need more than 100 numbers

【讨论】:

以上是关于如何为 T-SQL 选择中的每一行生成一个随机数?的主要内容,如果未能解决你的问题,请参考以下文章

如何为存在的每一行向 Spark 数据框中添加新列?

如何为 MySQL 中的每个组选择第一行?

如何为数据库中找到的每一行/记录生成一个链接,以便在单击时填充表单?

在反应状态引擎中,如何为结果表的每一行生成唯一的 UUID?

如何为网格面板中的每一行创建一个表单:extjs

SQL:如何为一列中具有重复值的每组行选择一行?