典型递归中的意外数据
Unexpected data at typical recursion
我很难用语言来形容这个,所以举个例子:
select *
into t
from (values (10, 'A'),
(25, 'B'),
(30, 'C'),
(45, 'D'),
(52, 'E'),
(61, 'F'),
(61, 'G'),
(61, 'H'),
(79, 'I'),
(82, 'J')
) v(userid, name)
请注意 F、G 和 H 具有相同的用户 ID。
现在,考虑以下递归查询:
with tn as
(
select t.userId,t.name, row_number() over (order by userid,newid()) as seqnum
from t
),
cte as
(
select userId, name, seqnum as seqnum
from tn
where seqnum = 1
union all
select tn.userId, tn.name,tn.seqnum
from
cte
inner join tn on tn.seqnum = cte.seqnum + 1
)
select *
from cte
第一个 cte,tn
,创建一个 row_number,其中包含一个随机化组件,它将导致 F/G/H 以随机顺序出现。但是,它们仍将全部出现一次(通过将最后一个和最外层的 from
修改为 from tn
即可轻松检查)
第二个cte,cte
,递归扫描tn
。它没有什么太复杂的,因为它来自一个最小化的例子。但是很明显,anchor成员被手动设置为tn
的第一行,然后递归扫描所有剩余的行。
然而,最终的结果集并没有F/G/H各出现一次!它们总共出现在 3 行中,但可以任意组合。 FGH 是可能的,但 FFH 也是可能的,甚至 FFF!这是一个 FHH 示例:
+--------+------+--------+
| userId | name | seqnum |
+--------+------+--------+
| 10 | A | 1 |
| 25 | B | 2 |
| 30 | C | 3 |
| 45 | D | 4 |
| 52 | E | 5 |
| 61 | F | 6 |
| 61 | H | 7 |
| 61 | H | 8 |
| 79 | I | 9 |
| 82 | J | 10 |
+--------+------+--------+
这是为什么?
我认为 与它没有任何关系,因为包含 row_number 的 tn
不是递归的。
郑重声明,由于以下事件顺序,我收到了这个问题:
有人问 that was answered by an excellent contributor。
同一个 OP 问 a follow-up question,虽然我可以通过对原件进行一些篡改来回答。然而,在深入挖掘之后,我发现了一个我无法理解的行为。我尽可能地简化了示例。
CTE 没有假脱机。每次引用 tn
都可能导致重新运行查询并重新随机化结果。
为避免这种情况,运行 随机化查询一次并加载临时文件 table。例如
select t.userId,t.name, row_number() over (order by userid,newid()) as seqnum
into #tn
from t
并在后续查询中引用它
with tn as
(
select * from #tn
),
cte as
(
select userId, name, seqnum as seqnum
from tn
where seqnum = 1
union all
select tn.userId, tn.name,tn.seqnum
from
cte
inner join tn on tn.seqnum = cte.seqnum + 1
)
select *
from cte
我很难用语言来形容这个,所以举个例子:
select *
into t
from (values (10, 'A'),
(25, 'B'),
(30, 'C'),
(45, 'D'),
(52, 'E'),
(61, 'F'),
(61, 'G'),
(61, 'H'),
(79, 'I'),
(82, 'J')
) v(userid, name)
请注意 F、G 和 H 具有相同的用户 ID。
现在,考虑以下递归查询:
with tn as
(
select t.userId,t.name, row_number() over (order by userid,newid()) as seqnum
from t
),
cte as
(
select userId, name, seqnum as seqnum
from tn
where seqnum = 1
union all
select tn.userId, tn.name,tn.seqnum
from
cte
inner join tn on tn.seqnum = cte.seqnum + 1
)
select *
from cte
第一个 cte,tn
,创建一个 row_number,其中包含一个随机化组件,它将导致 F/G/H 以随机顺序出现。但是,它们仍将全部出现一次(通过将最后一个和最外层的 from
修改为 from tn
即可轻松检查)
第二个cte,cte
,递归扫描tn
。它没有什么太复杂的,因为它来自一个最小化的例子。但是很明显,anchor成员被手动设置为tn
的第一行,然后递归扫描所有剩余的行。
然而,最终的结果集并没有F/G/H各出现一次!它们总共出现在 3 行中,但可以任意组合。 FGH 是可能的,但 FFH 也是可能的,甚至 FFF!这是一个 FHH 示例:
+--------+------+--------+
| userId | name | seqnum |
+--------+------+--------+
| 10 | A | 1 |
| 25 | B | 2 |
| 30 | C | 3 |
| 45 | D | 4 |
| 52 | E | 5 |
| 61 | F | 6 |
| 61 | H | 7 |
| 61 | H | 8 |
| 79 | I | 9 |
| 82 | J | 10 |
+--------+------+--------+
这是为什么?
我认为 tn
不是递归的。
郑重声明,由于以下事件顺序,我收到了这个问题:
有人问
CTE 没有假脱机。每次引用 tn
都可能导致重新运行查询并重新随机化结果。
为避免这种情况,运行 随机化查询一次并加载临时文件 table。例如
select t.userId,t.name, row_number() over (order by userid,newid()) as seqnum
into #tn
from t
并在后续查询中引用它
with tn as
(
select * from #tn
),
cte as
(
select userId, name, seqnum as seqnum
from tn
where seqnum = 1
union all
select tn.userId, tn.name,tn.seqnum
from
cte
inner join tn on tn.seqnum = cte.seqnum + 1
)
select *
from cte