生成 sql 服务器中的 table 中不存在的随机数

Generate a random number which is not there in a table in sql server

我正在寻找生成一个随机数,生成的数字在另一个 table 上不存在。

例如:如果一个名为 randomNums 的 table 具有值 10,20,30,40,50.

我想生成一个不同于上述值的数字。

我尝试了以下查询。

查询

;WITH CTE AS
(
    SELECT FLOOR(RAND()*100) AS rn
)
SELECT rn FROM CTE
WHERE rn NOT IN (SELECT num FROM randomNums);

但有时这个查询returns什么也没有。
因为那个时候它会生成 table randomNums.

中的数字

如何解决这个问题?

Fiddle for reference

尝试

declare @n as int

while @n is null and (select COUNT(*) from randomNums) < 100
Begin

;WITH CTE AS
(
    SELECT FLOOR(RAND()*100) AS rn
)
SELECT @n = rn FROM CTE
WHERE rn NOT IN (SELECT num FROM randomNums);

End

select @n

只有在排除的数量相对较少的情况下,才建议使用这种方法。

如果您不想使用 WHILE 循环,那么您可以查看这个采用递归 CTE:

的解决方案
;WITH CTE AS
(
    SELECT FLOOR(RAND()*100) AS rn  

    UNION ALL

    SELECT s.rn 
    FROM (
       SELECT rn      
       FROM CTE 
       WHERE rn NOT IN (SELECT num FROM randomNums)                         
       ) t
    CROSS JOIN (SELECT FLOOR(RAND()*100) AS rn) AS s
    WHERE t.rn IS NULL

)
SELECT rn
FROM CTE

编辑:

如下面的评论所述,以上内容不起作用:如果第一个生成的号码(来自 CTE 锚定成员)是 已经存在 的号码 randomNums,则递归成员的 CROSS JOIN 将 return NULL,因此锚成员的数字将 returned.

这是一个不同的版本,基于使用递归 CTE 的相同想法,有效:

DECLARE @maxAttempts INT = 100

;WITH CTE AS
(
   SELECT FLOOR(RAND()*100) AS rn,  
          1 AS i 

   UNION ALL

   SELECT FLOOR(RAND(CHECKSUM(NEWID()))*100) AS rn, i = i + 1
   FROM CTE AS c
   INNER JOIN randomNums AS r ON c.rn = r.num   
   WHERE (i = i) AND (i < @maxAttempts) 
)
SELECT TOP 1 rn
FROM CTE
ORDER BY i DESC

这里,CTE的主播成员首先生成一个随机数。如果此数字已经存在于 randomNums 中,则递归成员的 INNER JOIN 将成功,因此将生成另一个随机数。否则,INNER JOIN 将失败并且递归将终止。

还有几点需要注意:

  • i变量用于记录尝试生成'unique'随机数的次数。
  • i的值被用在递归成员的INNER JOIN操作中,以便与前一个递归的随机值相结合.
  • 由于使用相同的种子值 return 重复调用 RAND() 得到相同的结果,我们必须使用 CHECKSUM(NEWID()) 作为 RAND() 的种子。
  • @maxAttempts 可以选择性地用于指定为生成 'unique' 随机数而进行的最大尝试次数。

SQL Fiddle Demo here

查询

declare @RandomNums table (Num int);
insert into @RandomNums values (10),(20),(30),(40),(50),(60),(70),(80),(90);

-- Make a table of AvailableNumbers

with N as 
(
    select n from (values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10)) t(n)    
),
AvailableNumbers as 
(      
    select -- top 97 -- limit as you need
        row_number() over(order by (select 1)) as Number
    from 
        N n1, N n2 --, N n3, N n4, N n5, N n6 -- multiply as you need
),

-- Find which of AvailableNumbers is Vacant

VacantNumbers as
(
    select
        OrdinalNumber = row_number() over(order by an.Number) ,
        an.Number
    from
        AvailableNumbers an 
        left join @RandomNums rn on rn.Num = an.number
    where
        rn.Num is null
)

-- select rundom VacantNumber by its OrdinalNumber in VacantNumbers

select
    Number
from
    VacantNumbers
where
    OrdinalNumber = floor(rand()*(select count(*) from VacantNumbers) + 1);

还有一个选择,我一直喜欢 NEWID() 随机排序,交叉连接非常有效地创建许多行:

;with cte AS (SELECT 1 n UNION ALL SELECT 1)
     ,cte2 AS (SELECT TOP 100 ROW_NUMBER() OVER(ORDER BY a.n) n
               FROM cte a,cte b,cte c,cte d, cte e, cte f, cte g)
SELECT TOP 1 n
FROM cte2 a
WHERE NOT EXISTS (SELECT 1
                  FROM randomNums b
                  WHERE a.n = b.num)
ORDER BY NEWID()

演示:SQL Fiddle

另一种选择是为 table randomNums 创建唯一索引。然后在您的代码中捕获如果生成重复密钥可能出现的错误,在这种情况下选择另一个数字并重试。