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

标题说明了一切,为什么我不能在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子句中使用窗口函数的原因。

Crisfole asked 2020-02-22T18:28:05Z
8个解决方案
59 votes

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

一个答案,尽管不是特别有用,是因为规范说您不能。

请参阅Itzik Ben Gan的文章-逻辑查询处理:这是什么以及对您意味着什么,尤其是此处的图像。 在处理了所有WHERE/JOIN/GROUP BY/HAVING子句之后(步骤5.1),在剩余的结果集上,在col1 > 'B'时评估窗口函数。

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

col1 > 'B'子句中不允许使用它们的原因是,这会造成歧义。 使用窗口函数从高性能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'谓词在行编号之前或之后运行?

Martin Smith answered 2020-02-22T18:28:51Z
11 votes

无需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子句之后实际选择数据之前,不执行窗口功能。 因此,如果您尝试在WHERE子句中使用row_number,则该值尚未分配。

Taryn answered 2020-02-22T18:29:21Z
7 votes

首先,它叫做QUALIFY

“一次性操作”表示所有表达式都在同一位置   逻辑查询过程阶段在逻辑上同时进行评估。

以及一章对窗口功能的影响:

假设您有:

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;

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

情况1:

QUALIFY

结果:1002

情况2:

QUALIFY

结果:为空

所以我们有一个悖论。

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


附录

Terradata支持QUALIFY子句:

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

SELECT Id
FROM #Test
WHERE Id = 1002
QUALIFY ROW_NUMBER() OVER(ORDER BY Id) = 1;
Lukasz Szozda answered 2020-02-22T18:30:47Z
3 votes

您不一定需要使用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
Khan answered 2020-02-22T18:31:07Z
1 votes

是的,不幸的是,当您执行窗口函数时,即使您的谓词合法,SQL也会对您发火。 您可以在select语句中进行具有值的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.
djangojazz answered 2020-02-22T18:31:28Z
1 votes

最后,还有老式的,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
)

出于完整性考虑,我仅向您提供此信息。

Ann L. answered 2020-02-22T18:31:53Z
1 votes

这是一个老话题,但是我将尝试专门回答该主题中表达的问题。

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

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

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)

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

相反,WHERE子句之前的所有子句都不能引用SELECT子句中定义的任何列别名或派生列。 但是,这些列可以由诸如T-SQL子句之类的后续子句引用。

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

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

背后的原因是因为逻辑查询处理的工作方式在WHERE中。由于仅在逻辑查询处理到达SELECT步骤5.1时才建立基础查询结果。 (也就是说,在处理了T-SQLWHEREGROUP BYHAVING步骤之后),仅在查询的SELECTORDER BY子句中允许使用窗口函数。

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

同样,严格来说,在WHERE子句中不允许使用窗口函数的原因不是因为它会产生歧义,而是因为逻辑查询处理处理SELECT中的SELECT语句的顺序。

链接:这里,这里和这里

drumsta answered 2020-02-22T18:32:59Z
1 votes

基本上,第一个“ WHERE”子句条件是通过sql读取的,并且向表中查找了相同的列/值id,但在表中row_num = 1仍然不存在。 因此,它将无法正常工作。因此,我们将首先使用括号,然后再编写WHERE子句。

Ayush Garg answered 2020-02-22T18:33:20Z
translate from https://stackoverflow.com:/questions/13997177/why-no-windowed-functions-in-where-clauses