【问题标题】:recursive cte with ranking functions具有排名函数的递归 cte
【发布时间】:2011-07-21 22:50:19
【问题描述】:

如何在递归 cte 中使用排名函数? 这是一个简单的例子,展示了我正在尝试做的事情:

与 cte 作为 (
  select 1 a, 1 b union all select 1, 2 union all select 2, 3 union all select 2, 4
)
, rcte (a, b, c, d) 作为 (
  选择 a, b, cast(0 as int), 1
  来自 cte
  联合所有
  select a, b, cast(ROW_NUMBER() over (partition by a order by b) as int), d+1
  来自 rcte
  其中 d 

为什么没有排名?请告诉我我的错误

【问题讨论】:

  • 请用您期望的输出的详细信息更新问题

标签: sql-server tsql recursive-query


【解决方案1】:

编辑

当你阅读 CTE 关于递归的文档时,你会注意到它有一些限制,比如不能使用子查询、group-by、top。这些都涉及多行。从有限的测试,检查执行计划,以及测试这个查询

with cte as (
  select 1 a, 1 b union all select 1, 2 union all select 1, 3 union all select 2, 4
)
, rcte (a, b, c, d) as (
  select a, b, cast(0 as int), 1 
  from cte
  union all
  select r.a, cte.b, cast(ROW_NUMBER() over (order by r.b) as int), r.d+1
  from rcte r inner join cte on cte.a=r.a
  where r.d < 2
)
select * 
from rcte
where d=2
order by a, b

我只能得出结论:

  1. 当连接其他表以生成多行结果集时,Row_Number() 确实在 CTE 中工作
  2. 从编号结果来看,很明显 CTE 在所有迭代中都在单行中处理,逐行而不是逐行多行,即使它似乎同时迭代所有行。这可以解释为什么适用于多行操作的任何函数都不允许用于递归 CTE。

虽然我很容易得出这个结论,但显然有人花了更多时间explain it in excruciating detail 17个月前...

换句话说,这是 SQL Server 实现递归 CTE 的本质,因此窗口函数不会按您期望的方式工作。


为了他人的利益,输出是:
a           b           c           d
----------- ----------- ----------- -----------
1           1           1           2
1           2           1           2
2           3           1           2
2           4           1           2

而您期望 c 包含 1,2,1,2 而不是 1,1,1,1。 这似乎是一个错误,因为没有文档说窗口函数不应该在 CTE 的递归部分中工作。

注意:row_number() 返回 bigint,因此您可以只将 anchor(c) 转换为 bigint。

由于每次迭代都会增加 d,因此您可以在外部执行窗口。

with cte as (
  select 1 a, 1 b union all select 1, 2 union all select 2, 3 union all select 2, 4
)
, rcte (a, b, d) as (
  select a, b, 1 
  from cte
  union all
  select a, b, d+1
  from rcte
  where d < 2
)
select a,b, ROW_NUMBER() over (partition by a,d order by b) c,d
from rcte
--where d=2
order by d, a, b


编辑 - 洞察

在回答 another questionlink 时,我玩了一些递归 CTE。如果您在没有最终 ORDER BY 的情况下运行它,您可以看到 SQL Server 是如何接近递归的。有趣的是,在这种情况下它会倒退,然后对每一行进行完整的深度优先递归。

样本表

create table Testdata(SomeID int, OtherID int, Data varchar(max))
insert Testdata select 1, 9, '18,20,22,alpha,beta,gamma,delta'
insert Testdata select 2, 6, ''
insert Testdata select 3, 8, '11,12,.'
insert Testdata select 4, 7, '13,19,20,66,12,232,1232,12312,1312,abc,def'
insert Testdata select 5, 8, '17,19'

递归查询

;with tmp(SomeID, OtherID, DataItem, Data) as (
select SomeID, OtherID, LEFT(Data, CHARINDEX(',',Data+',')-1),
    STUFF(Data, 1, CHARINDEX(',',Data+','), '')
from Testdata
union all
select SomeID, OtherID, LEFT(Data, CHARINDEX(',',Data+',')-1),
    STUFF(Data, 1, CHARINDEX(',',Data+','), '')
from tmp
where Data > ''
)
select SomeID, OtherID, DataItem, Data
from tmp
-- order by SomeID

输出显示在第一次迭代中处理的 CTE 锚,然后无论出于何种原因锚集中的每一行都在处理其他行之前递归完成(深度优先)。

但它确实有其奇怪的用途,正如this answer 所示

【讨论】:

  • 感谢您的回复。但是,这对我来说不是可接受的解决方案。为了解决我的问题,我需要在 CTE 的递归部分中使用排名函数。有可能吗?
  • @yanzi - 请参阅编辑后的答案。这是不可能的。如果您可以将您的具体问题作为另一个问题提出,也许有人可以帮助您改写它。
  • @yanzi 您可能会发现 Quassnoi 的回答在这里提供了丰富的信息(扩展了这个答案的第 2 点)。 stackoverflow.com/questions/3187850/…
  • @Martin 感谢您的链接 - 已拼接到答案中
  • @Richard:一个完美的答案(不仅因为它链接到我的文章!),但这不是递归CTE 的本质,这是Microsoft 实现的本质递归CTE。排名函数在递归 CTE 中完美运行,正如在 PostgreSQLOracle 中实现的那样。
猜你喜欢
  • 2022-01-01
  • 2011-07-03
  • 2017-06-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-02-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多