sql-server - 窗口函数在子查询/CTE 中的行为不同?

标签 sql-server t-sql

我认为以下三个 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 吗?

最佳答案

logic order of operations SELECT 语句的内容对于理解第一个示例的结果很重要。从 Microsoft 文档来看,顺序是从上到下:

  1. FROM
  2. ON
  3. JOIN
  4. WHERE
  5. GROUP BY
  6. WITH CUBE or WITH ROLLUP
  7. HAVING
  8. SELECT
  9. DISTINCT
  10. ORDER BY
  11. TOP

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

正在过滤不含 CTE 的查询其中 id = 19。操作顺序导致 whereselect 子句中的窗口函数之前处理。只有 1 行 id 为 19。因此,where 将行限制为 id = 19,然后窗口函数才能处理无限前导之间的行和 1 前面。由于窗口函数没有行,因此 lastvalnull

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

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

(实际上,CTE 可以缩短并跳过生成数据,如果它可以这样做以提高性能而不影响结果。Itzik Ben-Gan 有一个 CTE great example,当它返回足够的数据时,它会跳过处理以满足查询。)

考虑一下如果将过滤器放入 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

关于sql-server - 窗口函数在子查询/CTE 中的行为不同?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43766672/

相关文章:

sql-server - 修改 Azure SQL 数据库中的文件最大大小

SQL Server SELECT STUFF 返回错误值

sql-server - 在 T-SQL 中,为什么 ISJSON 函数在 CTE 中使用时没有过滤掉不良数据?

sql-server - SQL Server 为一列创建多个非聚集索引 vs 在一个索引中创建多个列

sql - 在 SQL Server 的列中查找第一个匹配的字符串

sql-server - SQL Server : Clustering by timestamp; pros/cons

c# - 如何通过 OPENXML 解析存储过程中的 xml 序列化 List<int>?

sql - 将多种格式的字符串转换为单一标准格式

sql - 为不同的客户选择不同的最大ID

t-sql - 组过滤t-sql