生成sql server中表中不存在的随机数
我正在寻找生成一个随机数,生成的数字不在另一个表中 例如:如果一个名为randomNums的表的值为10,20,30,40,50 我喜欢在上面的值之外生成一个数字 我尝试了以下查询 质疑 但是有时候这个查询什么也不返回。 因为这一次它生成了表randomNums中的数字 如何解决这个问题 试一试生成sql server中表中不存在的随机数,sql,sql-server,sql-server-2008,Sql,Sql Server,Sql Server 2008,我正在寻找生成一个随机数,生成的数字不在另一个表中 例如:如果一个名为randomNums的表的值为10,20,30,40,50 我喜欢在上面的值之外生成一个数字 我尝试了以下查询 质疑 但是有时候这个查询什么也不返回。 因为这一次它生成了表randomNums中的数字 如何解决这个问题 试一试 只有在排除的数量相对较少的情况下,才建议使用这种方法。如果您不想使用WHILE循环,那么您可以研究使用递归CTE的解决方案: 编辑: 如下面的注释中所述,上述操作不起作用:如果CTE锚定成员生成的第一个
只有在排除的数量相对较少的情况下,才建议使用这种方法。如果您不想使用WHILE循环,那么您可以研究使用递归CTE的解决方案: 编辑: 如下面的注释中所述,上述操作不起作用:如果CTE锚定成员生成的第一个数字是randomNums中已经存在的数字,则递归成员的交叉连接将返回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的锚定成员首先生成随机数。如果这个数字已经存在于随机数中,递归成员的内部联接将成功,因此将生成另一个随机数。否则,内部连接将失败,递归将终止
还有几件事需要注意:
i变量用于记录尝试生成“唯一”随机数的次数。
在递归成员的内部联接操作中使用i的值,以便仅与前一个递归的随机值联接。
由于使用相同种子值的重复调用返回相同的结果,因此我们必须使用CHECKSUMNEWID作为RAND的种子。
@maxAttempts可选择性地用于指定最大尝试次数,以生成“唯一”随机数。
质疑
还有一个选项,我一直喜欢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()
演示:另一个选项是为表randomNums创建一个关于num值的唯一索引。然后,在代码中捕获生成重复密钥时可能出现的错误,在这种情况下,选择另一个数字并重试。尝试一下,看看是否有。当然,这取决于你的排除清单有多大。在caseIn实践中,您可以始终添加一个limit控件,只有当表包含从1到100的所有数字时,它才应该永远运行,也就是说,它不可能生成有效的数字,并且应该为此添加一个检查。即使如此,每增加一个数字,性能都会下降。如果这是一个问题,另一种方法是从表中的孔中选择,这是@lobodava的答案。哪种方法效果更好取决于你实际拥有的数字数量和范围。你要乘以100。这是否意味着您的范围在0到100之间?我会投票给您唯一有效的解决方案,它不是一个令人讨厌的循环。但是,如果您需要一个介于100万和100万之间的随机数,则此解决方案的工作效率不高,因为它需要每次排序100万个数字。对于大而稀疏的范围,迭代采样方法可以在时间上获胜。没错,在大范围内做到这一点的最快方法是使用一个适当索引的数字表。什么适当的索引可以提供快速随机数?如果必须按NEWID排序,即使是数字表也不会更快。除非你的意思是我们一劳永逸地制定随机序列-我认为这会起作用,只要你小心如何使用随机序列:-是的,你可以构建随机数序列,这取决于使用情况,以上内容非常适合手头的任务。我们不知道他们是在向表中添加随机数,还是出于其他目的不断地选择随机数,并且总是排除子集,如果不向排除表中添加,则预构建的列表将不起作用。下面是一个例子来证明这不起作用。@DavidG确实不起作用。如果第一个生成的数字包含在randomNums中,交叉连接将失败。@DavidG我想我现在已经修复了它!
;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
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
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);
;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()