Sql server 窗口函数在子查询/CTE中的行为是否不同?

Sql server 窗口函数在子查询/CTE中的行为是否不同?,sql-server,tsql,Sql Server,Tsql,我认为以下三条SQL语句在语义上是相同的。数据库引擎将在内部将第二个和第三个查询扩展到第一个查询 select .... from T where Id = 1 select * from (select .... from T) t where Id = 1 select * from (select .... from T where Id = 1) t 但是,我发现窗口函数的行为不同。我有以下代码 -- Prepare test data with t1

我认为以下三条SQL语句在语义上是相同的。数据库引擎将在内部将第二个和第三个查询扩展到第一个查询

select .... 
from T 
where Id = 1

select * 
from 
    (select .... from T) t 
where Id = 1

select * 
from 
    (select .... from T where Id = 1) t
但是,我发现窗口函数的行为不同。我有以下代码

-- Prepare test data
with t1 as 
( 
    select *
    from (values ( 2, null), ( 3, 10), ( 5, -1), ( 7, null), ( 11, null), ( 13, -12), ( 17, null), ( 19, null), ( 23, 1759) ) v ( id, col1 )
)
select * 
into #t 
from t1

alter table #t add primary key (id)
go
以下查询返回所有行

select  
     id, col1,
     cast(substring(max(cast(id as binary(4)) + cast(col1 as binary(4))) 
                       over (order by id
                             rows between unbounded preceding and 1 preceding), 5, 4) as int) as lastval
from    
    #t

id  col1    lastval
-------------------
2   NULL    NULL
3   10      NULL
5   -1      10
7   NULL    -1
11  NULL    -1
13  -12     -1
17  NULL    -12
19  NULL    -12
23  1759    -12
没有CTE/子查询:然后我添加了一个条件,只返回
Id=19的行

select  
    id, col1,
    cast(substring(max(cast(id as binary(4)) + cast(col1 as binary(4))) over (order by id rows between unbounded preceding and 1 preceding), 5, 4) as int) as lastval
from    
    #t
where
    id = 19;
但是,
lastval
返回
null

使用CTE/子查询:现在条件应用于CTE:

with t as 
( 
    select   
        id, col1,
        cast(substring(max(cast(id as binary(4)) + cast(col1 as binary(4))) over (order by id rows between unbounded preceding and 1 preceding ), 5, 4) as int) as lastval
    from     
        #t)
select *
from t
where id = 19;

-- Subquery
select  
    *
from
    (select   
         id, col1,
         cast(substring(max(cast(id as binary(4)) + cast(col1 as binary(4))) over (order by id rows between unbounded preceding and 1 preceding), 5, 4) as int) as lastval
     from     
         #t) t
where   
    id = 19;

现在
lastval
按预期返回
-12

窗口函数对结果集进行操作,因此当添加
其中id=19
时,结果集只有一行。由于窗口函数在无界的前一行和前一行之间指定
行,因此没有前一行,并导致
null


通过使用子查询/cte,您允许窗口函数在未过滤的结果集(前面的行存在的地方)上操作,然后仅从该结果集中检索那些行,其中id=19

您正在比较的查询不相等

select  id ,
        (... ) as lastval
from    #t
where   id = 19;
将只取1行,因此“lastval”将从col1中取NULL,因为窗口函数找不到前一行。

SELECT语句的输入是为了理解第一个示例的结果。从Microsoft文档中,从上到下的顺序是:

  • 加入
  • 在哪里
  • 分组
  • 使用多维数据集还是使用汇总
  • 拥有
  • 挑选
  • 明显的
  • 订购人
  • 请注意,
    WHERE
    子句处理逻辑上发生在
    SELECT
    子句之前

    正在筛选没有CTE的查询,其中id=19
    。操作顺序使
    where
    选择
    子句中的窗口函数之前进行处理。只有一行的
    id
    为19。因此,
    其中
    将行限制为id=19,然后窗口函数才能处理无界前一行和前一行之间的
    行。由于窗口函数没有行,因此
    lastval
    null

    将其与CTE进行比较。外部查询的过滤器尚未应用,因此CTE操作所有数据。无界前一行之间的
    行查找前一行。查询的外部部分将过滤器应用于中间结果,只返回已经具有正确的
    lastval
    的第19行

    您可以将CTE视为创建一个包含CTE数据的临时表。在将数据返回到外部查询之前,所有数据都被逻辑处理到一个单独的表中。示例中的CTE创建了一个临时工作表,其中包含前面行中的
    lastval
    。然后,应用外部查询中的过滤器,并将结果限制为
    id
    19

    (实际上,如果CTE能够在不影响结果的情况下提高性能,它可以缩短和跳过生成数据的过程。伊齐克·本·甘(Itzik Ben Gan)有一个CTE的例子,当它返回足够的数据以满足查询时,它会跳过处理。)

    考虑一下如果将过滤器放入CTE会发生什么情况。这应该与您提供的第一个示例查询完全相同。只有一行id=19,因此窗口函数找不到前面的任何行:

    with t as ( select id, col1,
                cast(substring(max(cast(id as binary(4)) + cast(col1 as binary(4))) over ( order by id
                   rows between unbounded preceding and 1 preceding ), 5, 4) as int) as lastval
                   from #t
                   where id = 19 -- moved filter inside CTE
                 )
        select  *
        from t
    

    因此,对于我问题顶部的前两种情况,我们不能因为窗口函数而假设它们在语义上是相同的。窗口函数是唯一导致差异的函数吗?因此,对于我问题顶部的前两种情况,我们不能因为窗口函数而假设它们在语义上是相同的。窗口函数是唯一导致差异的函数吗?过滤器的位置是导致差异的原因。如果过滤器位于CTE内,则CTE中的窗口功能仅限于该行。如果过滤器在CTE之外,则CTE的窗口函数处理所有行,外部查询只返回过滤后的行。