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

时间:2012-12-21 21:06:58

标签: sql sql-server tsql window-functions

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

8 个答案:

答案 0 :(得分:55)

  

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

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

参见Itzik Ben Gan的文章 - Logical Query Processing: What It Is And What It Means to You,特别是the image here。在处理了所有SELECT / WHERE / JOIN / GROUP BY条款后,在结果集HAVING上评估窗口函数(步骤5.1)。

  

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

WHERE子句中不允许它们的原因是它会产生歧义。从High-Performance T-SQL Using Window Functions(第25页)中偷取Itzik Ben Gan的例子

假设你的桌子是

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'谓词?

答案 1 :(得分:9)

不需要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_number子句中使用WHERE,则尚未分配该值。

答案 2 :(得分:7)

首先,它称为 all-at-once operation

  

&#34; All-at-Once Operations&#34;意味着所有表达式都相同   逻辑查询过程阶段在逻辑上同时进行评估。

很棒的章节对窗口函数的影响

假设你有:

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 )

结果:1​​002

案例2:

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

结果:空

  

所以我们有一个悖论。

     

此示例说明了为什么我们不能在WHERE子句中使用Window Functions。   您可以更多地考虑这个并找到Window Functions的原因   允许仅在 SELECT ORDER BY 子句中使用!

<强>附录

Terradata支持QUALIFY子句:

  

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

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

答案 3 :(得分:3)

您不一定需要使用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

答案 4 :(得分:1)

不幸的是,当你执行窗口函数时,即使你的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.

答案 5 :(得分: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
)

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

答案 6 :(得分:1)

这是一个旧帖子,但我会尝试专门回答该主题中表达的问题。

  

为什么在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子句在 底层查询结果 集中定义一个窗口或用户指定的行集,窗口函数计算该窗口的结果。< / p>

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

背后的原因是因为逻辑查询处理T-SQL中的工作方式。由于基础查询结果仅在逻辑查询处理到达SELECT步骤5.1时建立。 (即,在处理FROMWHEREGROUP BYHAVING步骤之后),仅在SELECT和{{1}中允许窗口函数查询的子句。

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

另外,严格地说,SELECT子句中不允许使用窗口函数的原因不是因为它会产生歧义,而是因为逻辑查询处理处理的顺序{{ 1}} {}}}中的陈述。

链接:hereherehere

答案 7 :(得分:1)

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