为什么CTE运行选择两次?

时间:2016-07-18 19:54:57

标签: sql sql-server tsql common-table-expression

我有两个CTE,第一个运行'select top n'来获取记录样本,然后第二个分割每个记录,每个记录产生2行。

我使用newid()来复制假数据,以确定样本的排序顺序。

declare @testtab table (Id bigint identity (1,1), ColA varchar(10), ColB varchar(10))
-- generate 1000 sample records
declare @cnt int = 0
while (@cnt<1000)
begin
    insert @testtab(ColA, ColB) values ('A'+convert(varchar,@cnt), 'B'+convert(varchar,@cnt))
    set @cnt+=1
end

;with SampleRecs as(
select top 1 * from @testtab order by newid()
)
,SplitRecs as (
select 0 Pos,Id,ColA Col from SampleRecs
union all
select 1, id, ColB col from SampleRecs
)
select * from SplitRecs
order by id, pos

预期的最终结果是每行输入行2行,如下所示:

Pos Id  Col
0   720 A719
1   720 A719

然而,我得到的是像

Pos Id  Col
0   720 A719
1   774 B773

因此,CTE中的“前1名”似乎在CTE2中运行了两次。

这是正常的吗?任何人都可以指出任何可以解释这种行为的文档吗?

1 个答案:

答案 0 :(得分:3)

这是因为您通过CTE在第二个CTE中引用了第一个UNION ALL两次。

CTE本质上是临时视图,在被调用后会被丢弃。因此,对第一个CTE的每次调用都会分别执行SELECT语句,并且由于您的ORDER BY已超过NEWID(),因此您可以获得不同的TOP 1两次执行的结果。

使用实际语句替换查询中的CTE引用有助于说明发生的情况:

SampleRecs定义为:

  

通过newid()

从@testtab顺序中选择前1 *

因此,用该子查询替换所有引用,将为您提供以下内容:

;With SplitRecs As
(
    Select  0       As Pos,
            Id,
            ColA    As Col
    From    (Select Top 1 * From @testtab Order By NewId()) As A
    Union All
    Select  1       As Pos,
            Id,
            ColB    As Col
    From    (Select Top 1 * From @testtab Order By NewId()) As B
)
Select  *
From    SplitRecs
Order By Id, pos

您将从此获得与CTE变体相同的行为。

要获得您期望的结果,您可以执行以下操作,相反,它利用CROSS JOINPos值设定种子:

Select      X.Pos   As Pos,
            Id,
            ColA    As Col
From        (Select Top 1 * From @testtab Order By NewId()) As A
Cross Join  (Select 0 As Pos Union All Select 1) As X
Order By    X.Pos