【问题标题】:SQL Condition on Window function窗口函数的 SQL 条件
【发布时间】:2016-02-11 07:18:23
【问题描述】:

我想对我的数据库(PostgreSQL v9.4.5)提出一个特殊要求,但我没能做到。

为了简单起见,假设我有下表AvgTemperatures,代表不同城市的不同平均温度,并以不同的时间长度计算(以月计):

 id |   city    |  avg | months 
----+-----------+------+--------
  1 |  New-York |   20 |     3   <--- average temperate over the last 3 months
  2 |  New-York |   19 |     6   <--- average temperate over the last 6 months
  3 |  New-York |   15 |    12   <--- etc
  4 |  New-York |   15 |    24
  5 |    Boston |   13 |     3
  6 |    Boston |   18 |     8
  7 |    Boston |   17 |    12
  8 |    Boston |   16 |    15
  9 |   Chicago |   12 |     2
 10 |   Chicago |   14 |    12
 11 |     Miami |   28 |     1
 12 |     Miami |   25 |     4
 13 |     Miami |   21 |    12
 14 |     Miami |   22 |    15
 15 |     Miami |   20 |    24

现在,假设我想在至少有一个平均温度超过 19 度的城市中选择与度量相关的所有行。在这种情况下,我想要:

 id |   city    |  avg | months 
----+-----------+------+--------
  1 |  New-York |   20 |     3  
  2 |  New-York |   19 |     6  
  3 |  New-York |   15 |    12  
  4 |  New-York |   15 |    24  
 11 |     Miami |   28 |     1  
 12 |     Miami |   25 |     4  
 13 |     Miami |   21 |    12  
 14 |     Miami |   22 |    15  
 15 |     Miami |   20 |    24  

我可以这样做:

 SELECT *
 FROM AvgTemperatures
 WHERE MIN(avg) OVER (PARTITION BY city) > 16

但是:

********** Erreur **********

ERROR: window functions not allowed in WHERE clause

而且,我不能使用GROUP BY

 SELECT *
 FROM AvtTemperatures
 GROUP BY city
 HAVING MIN(avg) > 16

因为我会因为聚合而丢失信息(顺便说一下,这个查询因为“SELECT *”而无效)。

我很确定我可以使用OVER PARTITION BY 来解决这个问题,但我不知道该怎么做。有人有想法吗?

【问题讨论】:

    标签: sql postgresql window-functions


    【解决方案1】:

    All-at-once operation:

    “All-at-Once Operations”是指所有表达式在同一个 逻辑查询过程阶段同时进行逻辑评估。

    还有精彩的章节对窗口函数的影响

    假设你有:

    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 子句中使用!

    要得到你想要的,你可以用CTE/subquery 包装窗口函数,就像Gordon answer

    ;WITH cte AS
    (
      SELECT t.*, MAX(AVG) OVER (PARTITION BY city) AS average
      FROM avgTemperatures t
    )
    SELECT *
    FROM cte
    where average > 19
    ORDER BY id;
    

    db<>fiddle demo

    输出:

    ╔═════╦══════════╦═════╦═════════╗
    ║ id  ║   city   ║ avg ║ months  ║
    ╠═════╬══════════╬═════╬═════════╣
    ║   1 ║ New-York ║  20 ║     3   ║
    ║   2 ║ New-York ║  19 ║     6   ║
    ║   3 ║ New-York ║  15 ║    12   ║
    ║   4 ║ New-York ║  15 ║    24   ║
    ║  11 ║ Miami    ║  28 ║     1   ║
    ║  12 ║ Miami    ║  25 ║     4   ║
    ║  13 ║ Miami    ║  21 ║    12   ║
    ║  14 ║ Miami    ║  22 ║    15   ║
    ║  15 ║ Miami    ║  20 ║    24   ║
    ╚═════╩══════════╩═════╩═════════╝
    

    【讨论】:

    • @a_horse_with_no_name 是 SQL Server 示例,但请解释为什么不能在 WHERE 子句中使用窗口函数
    【解决方案2】:

    您需要将其包装在派生表中才能在 where 子句中使用:

    select *
    from (
      SELECT t.*, MIN(avg) OVER (PARTITION BY city) as city_avg
      FROM AvgTemperatures t
    ) x
    WHERE city_avg > 16
    

    【讨论】:

      【解决方案3】:

      使用子查询获取最大值,然后使用where

      select t.*
      from (select t.*, max(avg) over (partition by city) as maxavg
            from avgTemperatures t
           ) t
      where maxavg > 19;
      

      另一种方法是在where 子句中执行此操作:

      select t.*
      from avgTemperatures t
      where t.city in (select t2.city from avgTemperatures t2 where t2.avg > 19);
      

      【讨论】:

        【解决方案4】:

        最简单的解决方案是使用bool_or aggregate function

        select id, city, avg, months
        from avttemperatures
        where city in (
            select city
            from avttemperatures
            group by 1
            having bool_or(avg > 19)
        )
        order by  2, 4
        ;
         id |   city   | avg | months 
        ----+----------+-----+--------
         11 | Miami    |  28 |      1
         12 | Miami    |  25 |      4
         13 | Miami    |  21 |     12
         14 | Miami    |  22 |     15
         15 | Miami    |  20 |     24
          1 | New-York |  20 |      3
          2 | New-York |  19 |      6
          3 | New-York |  15 |     12
          4 | New-York |  15 |     24
        

        测试表:

        create table avttemperatures (
            id int, city text, avg int, months int
        );
        insert into avttemperatures (id, city, avg, months) values
        (  1,'New-York',20,3),
        (  2,'New-York',19,6),
        (  3,'New-York',15,12),
        (  4,'New-York',15,24),
        (  5,'Boston',13,3),
        (  6,'Boston',18,8),
        (  7,'Boston',17,12),
        (  8,'Boston',16,15),
        (  9,'Chicago',12,2),
        ( 10,'Chicago',14,12),
        ( 11,'Miami',28,1),
        ( 12,'Miami',25,4),
        ( 13,'Miami',21,12),
        ( 14,'Miami',22,15),
        ( 15,'Miami',20,24);
        

        【讨论】:

          【解决方案5】:

          如果只想知道至少有一个是否存在,则无需聚合:

          SELECT id, city, avg, months
          FROM avgtemperatures t
          WHERE EXISTS ( SELECT 42
              FROM avgtemperatures x
              WHERE x.city = t.city
              AND x.avg > 19
              )
          ORDER BY city,months DESC
             ;
          

          注意:avg 是列的错误名称。

          【讨论】:

            猜你喜欢
            • 2022-01-12
            • 1970-01-01
            • 2017-07-15
            • 1970-01-01
            • 2018-01-25
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多