生成sql server中的表中没有的随机数

时间:2015-02-13 12:50:00

标签: sql sql-server sql-server-2008

我正在寻找生成一个随机数,其中生成的数字不在另一个表上。

例如:如果名为randomNums的表格的值为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);

但有时这个查询什么也不返回 因为那时它会生成表randomNums中的数字。

如何解决这个问题?

Fiddle for reference

5 个答案:

答案 0 :(得分:9)

另一种选择,我总是喜欢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

答案 1 :(得分:3)

如果您不想使用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将返回NULL,因此将返回锚成员中的数字。

这是一个不同的版本,基于使用递归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变量用于记录生成&#39; 随机数的尝试次数。
  • i的值在递归成员的INNER JOIN操作中使用,以便与紧接在递归的随机值连接< /强>
  • 由于具有相同种子值的RAND()的重复调用会返回相同的结果,因此我们必须使用CHECKSUM(NEWID())作为RAND()的种子。
  • @maxAttempts可以选择用于指定为了生成“唯一”而尝试的最大尝试次数。随机数。

SQL Fiddle Demo here

答案 2 :(得分:0)

<强>查询

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

答案 3 :(得分:0)

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

答案 4 :(得分:-1)

尝试

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

如果排除的数量相对较少,则建议使用此方法。