我正在寻找生成一个随机数,其中生成的数字不在另一个表上。
例如:如果名为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
中的数字。
如何解决这个问题?
答案 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
可以选择用于指定为了生成“唯一”而尝试的最大尝试次数。随机数。 答案 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
如果排除的数量相对较少,则建议使用此方法。