窗口函数在子查询/ CTE中的行为有所不同?

时间:2017-05-03 17:37:26

标签: 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 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

3 个答案:

答案 0 :(得分:2)

导入SELECT语句的logic order of operations以了解第一个示例的结果。从Microsoft文档中,订单是从上到下:

  
      
  1. FROM
  2.   
  3. ON
  4.   
  5. JOIN
  6.   
  7. WHERE
  8.   
  9. GROUP BY
  10.   
  11. WITH CUBE或WITH ROLLUP
  12.   
  13. HAVING
  14.   
  15. 选择
  16.   
  17. DISTINCT
  18.   
  19. ORDER BY
  20.   
  21. TOP
  22.   

请注意,WHERE子句处理在SELECT子句之前逻辑上发生。

正在过滤没有CTE的查询where id = 19。操作顺序使whereselect子句中的窗口函数之前处理。只有一行的id为19.因此,where在窗口函数处理rows between unbounded preceding and 1 preceding之前将行限制为id = 19。由于窗口函数没有行,lastvalnull

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

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

(实际上,CTE可以快捷方式并跳过生成数据,如果它可以提高性能而不影响结果.Izik Ben-Gan有一个great example的CTE,当它返回时会跳过处理足够的数据来满足查询。)

考虑如果将过滤器放在CTE中会发生什么。这应该与您提供的第一个示例查询完全相同。只有1行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

答案 1 :(得分:0)

窗口函数对结果集进行操作,因此当您添加where id = 19时,结果集只有1行。由于您的窗口函数指定rows between unbounded preceding and 1 preceding,因此没有前一行,并导致null

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

答案 2 :(得分:0)

您要比较的查询不等同。

select  id ,
        (... ) as lastval
from    #t
where   id = 19;

只需要1行,所以' lastval'将从col1获取NULL,因为窗口函数找不到前一行。