问题
我正在使用#Table_1
为CTE表this technique中的每一行生成一个随机数。然后我在另一张桌子#Table_2
上加入了CTE的结果。我没有为#Table_1
中的每一行获取一个随机数,而是为连接中的每个结果行获取一个新的随机数!
CREATE TABLE #Table_1 (Id INT)
CREATE TABLE #Table_2 (MyId INT, ParentId INT)
INSERT INTO #Table_1
VALUES (1), (2), (3)
INSERT INTO #Table_2
VALUES (1, 1), (2, 1), (3, 1), (4, 1), (1, 2), (2, 2), (3, 2), (1, 3)
;WITH RandomCTE AS
(
SELECT Id, (ABS(CHECKSUM(NewId())) % 5)RandomNumber
FROM #Table_1
)
SELECT r.Id, t.MyId, r.RandomNumber
FROM RandomCTE r
INNER JOIN #Table_2 t
ON r.Id = t.ParentId
结果
Id MyId RandomNumber
----------- ----------- ------------
1 1 1
1 2 2
1 3 0
1 4 3
2 1 4
2 2 0
2 3 0
3 1 3
期望的结果
Id MyId RandomNumber
----------- ----------- ------------
1 1 1
1 2 1
1 3 1
1 4 1
2 1 4
2 2 4
2 3 4
3 1 3
我尝试了什么
我试图通过将随机数转换为VARCHAR
来模糊优化器中随机数生成的逻辑,但这不起作用。
我不想做什么
我想避免使用临时表来存储CTE的结果。
如何为表生成随机数并在连接中保留该随机数而不使用临时存储?
这似乎可以解决问题:
WITH CTE AS(
SELECT Id, (ABS(CHECKSUM(NewId())) % 5)RandomNumber
FROM #Table_1),
RandomCTE AS(
SELECT Id,
RandomNumber
FROM CTE
GROUP BY ID, RandomNumber)
SELECT *
FROM RandomCTE r
INNER JOIN #Table_2 t
ON r.Id = t.ParentId;
看起来SQL Server意识到,在CTE之外,RandomNumber
实际上只是NEWID()
,其中包含一些额外的功能(DB<>Fiddle),因此它仍然为每一行生成一个唯一的ID。因此,第二个CTE中的GROUP BY
子句强制数据引擎将RandomNumber定义为一个值,以便它可以执行GROUP BY
。
根据this answer的报价
优化器不保证标量函数的执行时间或执行次数。这是一个长期建立的宗旨。它是基本的“余地”,它允许优化器有足够的自由度来获得查询计划执行方面的重大改进。
如果对您的应用程序来说重要的是,应该对随机数进行一次评估,并且只应该预先计算一次并将其存储到临时表中。
其他任何事情都不能得到保证,因此添加到您的应用程序的代码库中是不负责任的 - 即使它现在可以正常工作,它也可能因架构更改/执行计划更改/版本升级/ CU安装而中断。
例如,如果向#Table_1 (Id)
添加唯一索引,拉姆的答案就会中断
如何不使用真正的随机数?使用rand()
种子:
WITH RandomCTE AS (
SELECT Id,
CONVERT(INT, RAND(ROW_NUMBER() OVER (ORDER BY NEWID()) * 999999) * 5) as RandomNumber
FROM #Table_1
)
SELECT r.Id, t.MyId, r.RandomNumber
FROM RandomCTE rINNER JOIN
#Table_2 t
ON r.Id = t.ParentId;
rand()
的种子争论非常糟糕。彼此接近的种子的值产生相似的初始值,这是乘法的原因。
Here是db <>小提琴。