选择第一个&窗口中的最后日期

时间:2014-12-01 15:08:01

标签: sql postgresql aggregate-functions window-functions

我正在努力选择第一个&基于月份和时间的窗口中的最后日期提供的年份。

以下是示例数据:

F.rates
| id | c_id | date       | rate |
---------------------------------
| 1  | 1    | 01-01-1991 | 1    |
| 1  | 1    | 15-01-1991 | 0.5  |
| 1  | 1    | 30-01-1991 | 2    |
.................................
| 1  | 1    | 01-11-2014 | 1    |
| 1  | 1    | 15-11-2014 | 0.5  |
| 1  | 1    | 30-11-2014 | 2    |

这是我提出的pgSQL SELECT:

SELECT c_id, first_value(date) OVER w, last_value(date) OVER w FROM F.rates 
WINDOW w AS (PARTITION BY EXTRACT(YEAR FROM date), EXTRACT(MONTH FROM date), c_id 
             ORDER BY date ASC)

这给了我一个非常接近我想要的结果:

| c_id | first_date | last_date  |
----------------------------------
| 1    | 01-01-1991 | 15-01-1991 |
| 1    | 01-01-1991 | 30-01-1991 |
.................................

应该是:

| c_id | first_date | last_date  |
----------------------------------
| 1    | 01-01-1991 | 30-01-1991 |
.................................

由于某些原因last_value(date)返回窗口中的每条记录。这让我觉得我误解了SQL中的窗口是如何工作的。这就像SQL为它迭代的每一行形成一个新窗口,而不是基于YEAR和MONTH的整个表的多个窗口。

任何人都可以善良并解释我是否错了,我如何达到我想要的结果?

我之所以没有在GROUP BY子句中使用MAX / MIN,是有原因的。我的下一步是检索我选择的日期的相关费率,例如:

| c_id | first_date | last_date  | first_rate | last_rate  | avg rate |
-----------------------------------------------------------------------
| 1    | 01-01-1991 | 30-01-1991 | 1          | 2          | 1.1      |
.......................................................................

2 个答案:

答案 0 :(得分:2)

如果您希望将输出分组为单个(或更少)行,则应使用简单聚合(即GROUP BY),如果avg_rate足够:

SELECT c_id, min(date), max(date), avg(rate)
FROM F.rates
GROUP BY c_id, date_trunc('month', date)

有关PostgreSQL's documentation中的窗口函数的更多信息:

  

但与常规聚合函数不同,使用窗口函数不会导致行被分组到单个输出行中 - 行保留其独立的标识。

     

...

     

还有另一个与窗口函数相关的重要概念:对于每一行,其分区中都有一组称为窗口框架的行。许多(但不是全部)窗口函数仅作用于窗口框架的行,而不是整个分区。 默认情况下,如果提供了ORDER BY,则该框架包含分区的所有行 到当前行 ,以及根据ORDER BY子句等于当前行的任何后续行。省略ORDER BY时,默认框架由分区中的所有行组成。

     

...

     

可以选择以其他方式定义窗口框架...有关详细信息,请参阅Section 4.2.8

修改

如果您要折叠(最小/最大聚合)您的数据并希望收集的列数多于GROUP BY中列出的列数,则您有两个选择:

SQL方式

在子查询中选择最小/最大值,然后将其原始行连接起来(但是这样,你必须处理这样一个事实,即min / max-ed列通常不是唯一的) :

SELECT c_id,
       min first_date,
       max last_date,
       first.rate first_rate,
       last.rate last_rate,
       avg avg_rate
FROM   (SELECT   c_id, min(date), max(date), avg(rate)
        FROM     F.rates
        GROUP BY c_id, date_trunc('month', date)) agg
JOIN   F.rates first ON agg.c_id = first.c_id AND agg.min = first.date
JOIN   F.rates last  ON agg.c_id = last.c_id  AND agg.max = last.date

PostgreSQL' DISTINCT ON

DISTINCT ON通常用于此任务,但高度依赖于排序(一次只能以这种方式搜索1个极值):

SELECT   DISTINCT ON (c_id, date_trunc('month', date))
         c_id,
         date first_date,
         rate first_rate
FROM     F.rates
ORDER BY c_id, date

您可以将此查询与F.rates的其他聚合子查询相关联,但这一点(如果您确实需要最小和最大,在您的情况下甚至是平均值),SQL兼容的方式更适合

答案 1 :(得分:1)

窗口函数不适用于此。改为使用聚合函数。

select 
    c_id, date_trunc('month', date)::date, 
    min(date) first_date, max(date) last_date
from rates
group by c_id, date_trunc('month', date)::date;
 c_id | date_trunc | first_date | last_date
------+------------+------------+------------
    1 | 2014-11-01 | 2014-11-01 | 2014-11-30
    1 | 1991-01-01 | 1991-01-01 | 1991-01-30

create table rates (
  id integer not null,
  c_id integer not null,
  date date not null,
  rate numeric(2, 1),
  primary key (id, c_id, date)
);

insert into rates values
(1, 1, '1991-01-01', 1),
(1, 1, '1991-01-15', 0.5),
(1, 1, '1991-01-30', 2),
(1, 1, '2014-11-01', 1),
(1, 1, '2014-11-15', 0.5),
(1, 1, '2014-11-30', 2);