典型递归中的意外数据

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