Tl;DR
与使用临时表相比,使用 CTE 时得到不同的结果。为什么?
问题
假设我有以下数据结构:
#numbers
从1到10的简单数字表。
DROP TABLE IF EXISTS #numbers;
WITH rcte(n) AS (
SELECT 1 AS n
UNION ALL
SELECT n + 1 AS n
FROM rcte
WHERE n < 10
)
SELECT n
INTO #numbers
FROM rcte;
SELECT TOP 3 n
FROM #numbers;
n |
---|
1 |
2 |
3 |
#max
我现在可以使用
#numbers
创建一个包含 3 行和 3 列 id_col
和 max_nr
(2 到 5 之间的随机数)的表格:
DROP TABLE IF EXISTS #max;
DECLARE @min TINYINT = 2
, @max TINYINT = 5;
SELECT CONCAT(N'id_', n) AS id_col
, ABS(CHECKSUM(NEWID())) % (@max - @min + 1) + @min AS max_nr
INTO #max
FROM #numbers
WHERE n <= 3;
SELECT *
FROM #max; --- max_nr will of course look different each run
id_col | 最大_nr |
---|---|
id_1 | 5 |
id_2 | 2 |
id_3 | 3 |
#result
现在我可以轻松生成我需要的表格,即一个表格,其中对于每个
id_col
我都有最多 max_nr
的每个数字
DROP TABLE IF EXISTS #result;
SELECT id_col
, n
FROM #max
INNER JOIN #numbers
ON n <= max_nr;
id_col | n |
---|---|
id_1 | 1 |
id_1 | 2 |
id_1 | 3 |
id_1 | 4 |
id_1 | 5 |
id_2 | 1 |
id_2 | 2 |
id_3 | 1 |
id_3 | 2 |
id_3 | 3 |
CTE
如果我尝试对 A
CTE
做同样的事情,我有时会得到(取决于随机数)奇怪的结果。我的猜测是,为连接中的每一行重新计算随机部分,导致不同的结果,如果是这种情况,是使用临时表的唯一解决方案,或者有机会用 CTE
来解决这个问题?
NB. 重新运行以下代码几次,您最终会发现有“漏洞”,即缺少数字:
WITH cte_max (id_col, max_nr) AS (
SELECT CONCAT(N'id_', n) AS id_col
, ABS(CHECKSUM(NEWID())) % (@max - @min + 1) + @min AS max_nr
FROM #numbers
WHERE n <= 3
)
SELECT id_col
, n
FROM cte_max
INNER JOIN #numbers
ON n <= max_nr;
例如,我在一次运行中得到了这个结果:
id_col | n | 评论 |
---|---|---|
id_1 | 1 | |
id_1 | 2 | |
id_1 | 4 | 3 失踪!! |
id_1 | 5 | |
id_2 | 1 | |
id_2 | 2 | |
id_2 | 3 | |
id_3 | 1 | |
id_3 | 2 |
你看到三个不见了。并不是所有的运行都会发生这种情况(有时所有数字都是有序的,但它们时不时会丢失,我清楚地看到我忽略了一些东西)
问题
有人可以向我解释一下为什么会出现这种行为吗? SQL Server 并行化其任务以及在内连接中随机数尚未具体化并为每个连接结果重新计算是否与此有关?如果是这样,除了使用临时表之外还有其他解决方法吗?
RAND()
执行一次随机操作,我们可以在 WITH
: 中依赖它的值
create table numbers(nr int);
insert into numbers(nr)
values
(1),
(2),
(3),
(4),
(5),
(6),
(7),
(8),
(9),
(10);
create table other_numbers(nr int);
insert into other_numbers(nr)
values
(1),
(2),
(3),
(4),
(5),
(6),
(7),
(8),
(9),
(10);
WITH rand_num (nr, randomized) AS (
SELECT nr,
CEILING(10 * RAND()) AS random
FROM numbers
)
SELECT rand_num.nr AS nr1, other_numbers.nr AS nr2, rand_num.randomized
FROM rand_num
JOIN other_numbers
ON rand_num.nr = other_numbers.nr;