我认为以下三个 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 文档来看,顺序是从上到下:
- FROM
- ON
- JOIN
- WHERE
- GROUP BY
- WITH CUBE or WITH ROLLUP
- HAVING
- SELECT
- DISTINCT
- ORDER BY
- TOP
请注意,WHERE
子句处理逻辑上发生在 SELECT
子句之前。
正在过滤不含 CTE 的查询其中 id = 19
。操作顺序导致 where
在 select
子句中的窗口函数之前处理。只有 1 行 id
为 19。因此,where
将行限制为 id = 19,然后窗口函数才能处理无限前导之间的行和 1 前面
。由于窗口函数没有行,因此 lastval
为 null
。
将其与 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/