【问题标题】:Why no windowed functions in where clauses?为什么 where 子句中没有窗口函数?
【发布时间】:2012-12-09 10:46:51
【问题描述】:

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

【问题讨论】:

  • 开窗函数是非关系层的一部分(因为关系理论不处理有序数据);因此,它们会在其他所有内容之后进行评估。

标签: sql sql-server tsql window-functions


【解决方案1】:

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

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

请参阅 Itzik Ben Gan 的文章 - Logical Query Processing: What It Is And What It Means to You,尤其是 the image here。在处理完所有 WHERE/JOIN/GROUP BY/HAVING 子句后剩余的结果集上的 SELECT 时对窗口函数进行评估(步骤 5.1)。

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

WHERE 子句中不允许使用它们的原因是它会产生歧义。从 High-Performance T-SQL Using Window Functions 窃取 Itzik Ben Gan 的示例(第 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 &gt; 'B' 谓词在行编号之前或之后运行?

【讨论】:

  • 这与他们在GROUP BY clause中不允许使用的原因相同吗??
  • @MahmoudGamal 在 group by 中操作的窗口需要与 select 中使用的窗口不同,因为这是分组和拥有后的结果。假设您可以将其定义为逻辑上出现在 group by 之前的行,但我认为如果允许这样做会很混乱。
  • 这似乎很懒惰。我们在 select 之前运行“where”和“group”等规则,在 select 之后运行 order by。为什么不直接选择这样的规则?
  • @RaviR 他们选择了这样的规则。窗口函数几乎在其他所有内容之后运行。这就是为什么你不能在 WHERE 中使用它们
【解决方案2】:

不需要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,则尚未分配该值。

【讨论】:

  • +1 用于回答问题,但不是我想要的……我要再问一次吗,这次正确吗?
  • @ChristopherPfohl 根据我的理解,在选择记录之前不会分配 row_number,因此您不能在 WHERE 子句中使用它,因为该值尚不存在。
  • 为给予和带走道歉,但下面的答案更接近我正在寻找的答案。
  • @ChristopherPfohl 这与我在评论中所说的基本相同,但您可以决定接受什么答案。 :)
  • 这不是问题。当然有办法绕过它,但问题是为什么它不允许在哪里。
【解决方案3】:

"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 )

结果:1​​002

案例 2:

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:

Snowflake - Qualify

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

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

来自

    Where

    Group by

    Having

    Window

    QUALIFY

    Distinct

    Order by

    Limit

【讨论】:

    【解决方案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
    

    【讨论】:

    • +1 用于回答问题,但不是我想要的……我要再问一次吗,这次正确吗?
    【解决方案5】:

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

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

    SELECT 语句在键入顺序中指定了以下主要子句:

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

    Logical Query Processing Order,或者Binding Order,是概念上的解释顺序,它定义了查询的正确性。此顺序决定了一个步骤中定义的对象何时可用于后续步骤中的子句。

    ----- 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 中的工作方式。由于底层查询结果只有在逻辑查询处理到达SELECT步骤5.1时才建立。 (即在处理完FROMWHEREGROUP BYHAVING 步骤之后),窗口函数只允许在查询的SELECTORDER BY 子句中使用。

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

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

    链接:hereherehere

    【讨论】:

      【解决方案6】:

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

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

      【讨论】:

        【解决方案7】:

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

        【讨论】:

          【解决方案8】:

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

          【讨论】:

            猜你喜欢
            • 2014-03-26
            • 2017-01-15
            • 1970-01-01
            • 2020-11-29
            • 1970-01-01
            • 2017-05-16
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多