窗口函数的SQL条件

时间:2015-11-10 12:02:14

标签: sql postgresql window-functions

我想在我的数据库上做一个特殊的请求(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来解决这个问题,但我不知道怎么做。有人有想法吗?

5 个答案:

答案 0 :(得分:5)

首先,它称为 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子句中使用Window Functions。   您可以更多地考虑这个并找到Window Functions的原因   允许仅在 SELECT ORDER BY 子句中使用!

为了得到你想要的东西你可以用Gordon answer中的CTE/subquery包装窗口函数:

;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;

LiveDemo

输出:

╔═════╦══════════╦═════╦═════════╗
║ 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   ║
╚═════╩══════════╩═════╩═════════╝

答案 1 :(得分:2)

最简单的解决方案是使用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);

答案 2 :(得分:2)

如果您只想知道是否存在至少一个,则无需汇总:

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是列的错误名称。

答案 3 :(得分:1)

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

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

答案 4 :(得分:1)

使用子查询获取最大值,然后使用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);