65

标题说明了一切,为什么我不能在 SQL Server 的 where 子句中使用窗口函数?

这个查询很有意义:

select id, sales_person_id, product_type, product_id, sale_amount
from Sales_Log
where 1 = row_number() over(partition by sales_person_id, product_type, product_id order by sale_amount desc)

但它不起作用。有比 CTE/子查询更好的方法吗?

编辑

值得一提的是带有 CTE 的查询:

with Best_Sales as (
    select id, sales_person_id, product_type, product_id, sale_amount, row_number() over (partition by sales_person_id, product_type, product_id order by sales_amount desc) rank
    from Sales_log
)
select id, sales_person_id, product_type, product_id, sale_amount
from Best_Sales
where rank = 1

编辑

+1 用于显示子查询的答案,但实际上我正在寻找无法在 where 子句中使用窗口函数的原因。

4

8 回答 8

74

为什么我不能在 SQL Server 的 where 子句中使用窗口函数?

一个答案,虽然不是特别有用,是因为规范说你不能。

请参阅 Itzik Ben Gan 的文章 -逻辑查询处理:它是什么以及它对您意味着什么,尤其是这里的图像。窗口函数在处理完所有///子句SELECT后剩余的结果集上进行评估(步骤 5.1)。WHEREJOINGROUP BYHAVING

真的,我正在寻找无法在 where 子句中使用窗口函数的原因。

条款中不允许使用它们的原因WHERE是它会产生歧义。使用窗口函数从高性能 T-SQL 中窃取 Itzik Ben Gan 的示例(p.25)

假设你的桌子是

CREATE TABLE T1
(
col1 CHAR(1) PRIMARY KEY
)

INSERT INTO T1 VALUES('A'),('B'),('C'),('D'),('E'),('F')

而你的查询

SELECT col1
FROM T1
WHERE ROW_NUMBER() OVER (ORDER BY col1) <= 3
AND col1 > 'B'

什么是正确的结果?您是否期望col1 > 'B'谓词在行编号之前或之后运行?

于 2012-12-21T21:24:58.827 回答
15

不需要 CTE,只需在子查询中使用窗口函数即可:

select id, sales_person_id, product_type, product_id, sale_amount
from
(
  select id, sales_person_id, product_type, product_id, sale_amount,
    row_number() over(partition by sales_person_id, product_type, product_id order by sale_amount desc) rn
  from Sales_Log
) sl
where rn = 1

编辑,将我的评论移至答案。

WHERE在子句之后的数据被实际选择之前,不会执行窗口函数。因此,如果您尝试row_numberWHERE子句中使用 a ,则尚未分配该值。

于 2012-12-21T21:12:29.293 回答
10

“All-at-once operation”是指同一逻辑查询过程阶段中的所有表达式同时进行逻辑求值。

和伟大的章节对窗口功能的影响

假设你有:

CREATE TABLE #Test ( Id INT) ;
 
INSERT  INTO #Test VALUES  ( 1001 ), ( 1002 ) ;

SELECT Id
FROM #Test
WHERE Id = 1002
  AND ROW_NUMBER() OVER(ORDER BY Id) = 1;

All-at-Once 操作告诉我们这两个条件在同一时间点进行逻辑评估。因此,SQL Server 可以根据估计的执行计划以任意顺序评估 WHERE 子句中的条件。所以这里的主要问题是首先评估哪个条件。

情况1:

If ( Id = 1002 ) is first, then if ( ROW_NUMBER() OVER(ORDER BY Id) = 1 )

结果:1002

案例二:

If ( ROW_NUMBER() OVER(ORDER BY Id) = 1 ), then check if ( Id = 1002 )

结果:空

所以我们有一个悖论。

这个例子说明了为什么我们不能在 WHERE 子句中使用窗口函数。您可以对此进行更多思考,并找出为什么只允许在SELECTORDER BY子句中使用窗口函数!


附录

Teradata 支持QUALIFY子句:

根据用户指定的搜索条件过滤先前计算的有序分析函数的结果。

SELECT Id
FROM #Test
WHERE Id = 1002
QUALIFY ROW_NUMBER() OVER(ORDER BY Id) = 1;

附录 2:

雪花 - 合格

QUALIFY 对窗口函数的作用与 HAVING 对聚合函数和 GROUP BY 子句的作用相同。

因此,在查询的执行顺序中,QUALIFY 在计算窗口函数之后进行评估。通常,SELECT 语句的子句按如下所示的顺序进行评估:

    Where

    Group by

    Having

    Window

    QUALIFY

    Distinct

    Order by

    Limit
于 2015-11-04T12:43:40.503 回答
4

不一定需要使用CTE,可以使用row_number() 后查询结果集

select row, id, sales_person_id, product_type, product_id, sale_amount
from (
    select
        row_number() over(partition by sales_person_id, 
            product_type, product_id order by sale_amount desc) AS row,
        id, sales_person_id, product_type, product_id, sale_amount
    from Sales_Log 
    ) a
where row = 1
于 2012-12-21T21:12:41.947 回答
2

这是一个旧线程,但我会尝试具体回答该主题中表达的问题。

为什么 where 子句中没有窗口函数?

SELECT语句具有以键入顺序指定的以下主要子句:

SELECT DISTINCT TOP list
FROM  JOIN ON / APPLY / PIVOT / UNPIVOT
WHERE
GROUP BY  WITH CUBE / WITH ROLLUP
HAVING
ORDER BY
OFFSET-FETCH

逻辑查询处理顺序,或绑定顺序,是概念上的解释顺序,它定义了查询的正确性。此顺序确定一个步骤中定义的对象何时可用于后续步骤中的子句。

----- Relational result
  1. FROM
    1.1. ON JOIN / APPLY / PIVOT / UNPIVOT
  2. WHERE
  3. GROUP BY
    3.1. WITH CUBE / WITH ROLLUP
  4. HAVING
  ---- After the HAVING step the Underlying Query Result is ready
  5. SELECT
    5.1. SELECT list
    5.2. DISTINCT
----- Relational result

----- Non-relational result (a cursor)
  6. ORDER BY
  7. TOP / OFFSET-FETCH
----- Non-relational result (a cursor)

例如,如果查询处理器可以绑定到(访问)FROM子句中定义的表或视图,则这些对象及其列可用于所有后续步骤。

相反,子句之前的所有SELECT子句都不能引用子句中定义的任何列别名或派生列SELECT。但是,这些列可以被后续子句(例如ORDER BY子句)引用。

OVER子句在应用关联的窗口函数之前确定行集的分区和排序。也就是说,该子句在基础查询结果OVER集中定义一个窗口或用户指定的一组行,并且窗口函数根据该窗口计算结果。

Msg 4108, Level 15, State 1, …
Windowed functions can only appear in the SELECT or ORDER BY clauses.

背后的原因是因为逻辑查询处理的工作方式T-SQL。由于只有在逻辑查询处理到步骤 5.1时才建立底层查询结果。SELECT(即在处理FROMWHERE和步骤之后) GROUP BYHAVING窗口函数只允许在查询的SELECTandORDER BY子句中使用。

需要注意的是,即使关系模型不处理有序数据,窗口函数仍然是关系层的一部分。SELECT步骤 5.1之后的结果。与任何窗口函数仍然是相关的。

另外,严格来说,子句中不允许使用窗口函数的原因WHERE并不是因为它会造成歧义,而是因为逻辑查询处理SELECT的语句处理顺序T-SQL

链接:这里这里这里

于 2018-05-10T19:25:34.233 回答
1

最后,还有老式的 SQL Server 2005 之前的方式,带有一个相关的子查询:

select *
from   Sales_Log sl
where  sl.id = (
    Select Top 1 id
    from   Sales_Log sl2
    where  sales_person_id = sl.sales_person_id
       and product_type = sl.product_type
       and product_id = sl.product_id
    order by sale_amount desc
)

我给你这个只是为了完整。

于 2012-12-21T21:19:47.690 回答
1

基本上第一个“WHERE”子句条件由 sql 读取,并且相同的列/值 id 查看了表,但表中的 row_num=1 仍然不存在。因此它不会起作用。这就是我们将首先使用括号然后我们将编写 WHERE 子句的原因。

于 2018-11-09T11:23:42.130 回答
0

是的,不幸的是,当您执行窗口函数时,即使您的 where 谓词是合法的,SQL 也会生您的气。您在 select 语句中创建一个具有该值的 cte 或嵌套选择,然后稍后使用该值引用您的 CTE 或嵌套选择。应该是不言自明的简单示例。如果您真的讨厌 cte 在处理大型数据集时遇到一些性能问题,您可以随时使用临时表或表变量。

declare @Person table ( PersonID int identity, PersonName varchar(8));

insert into @Person values ('Brett'),('John');

declare @Orders table ( OrderID int identity, PersonID int, OrderName varchar(8));

insert into @Orders values (1, 'Hat'),(1,'Shirt'),(1, 'Shoes'),(2,'Shirt'),(2, 'Shoes');

--Select
--  p.PersonName
--, o.OrderName
--, row_number() over(partition by o.PersonID order by o.OrderID)
--from @Person p 
--  join @Orders o on p.PersonID = o.PersonID
--where row_number() over(partition by o.PersonID order by o.orderID) = 2

-- yields:
--Msg 4108, Level 15, State 1, Line 15
--Windowed functions can only appear in the SELECT or ORDER BY clauses.
;

with a as 
    (
    Select
    p.PersonName
,   o.OrderName
,   row_number() over(partition by o.PersonID order by o.OrderID) as rnk
from @Person p 
    join @Orders o on p.PersonID = o.PersonID
    )
select *
from a 
where rnk >= 2 -- only orders after the first one.
于 2012-12-21T21:16:30.310 回答