Выберите первую и последнюю дату в окне

Я пытаюсь выбрать первую и последнюю дату в окне в зависимости от месяца и года указанной даты.

Вот пример данных:

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.

Так может ли кто-нибудь быть добрым и объяснить, если я ошибаюсь, и как мне достичь желаемого результата?

Есть причина, по которой я не использую MAX / MIN по предложению GROUP BY. Следующим моим шагом будет получение связанных ставок для выбранных мной дат, например:

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

person Gening D.    schedule 01.12.2014    source источник
comment
Кстати, извините за плохой английский. Я изо всех сил стараюсь учиться :)   -  person Gening D.    schedule 01.12.2014


Ответы (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:

Но в отличие от обычных агрегатных функций, использование оконной функции не приводит к группированию строк в одну строку вывода - строки сохраняют свои отдельные идентификаторы.

...

Есть еще одна важная концепция, связанная с оконными функциями: для каждой строки есть набор строк в ее разделе, который называется оконной рамкой. Многие (но не все) оконные функции действуют только на строки оконной рамы, а не на весь раздел. По умолчанию, если указано ORDER BY, то фрейм состоит из всех строк с начала раздела вверх до текущей строки плюс любые последующие строки, которые равны текущей строке в соответствии с предложением ORDER BY. Если ORDER BY не указан, фрейм по умолчанию состоит из всех строк в разделе.

...

Есть варианты определения оконной рамы другими способами ... См. раздел 4.2.8 для получения дополнительной информации.

ИЗМЕНИТЬ:

Если вы хотите свернуть (агрегирование минимум / максимум) свои данные и хотите собрать больше столбцов, чем перечислено в GROUP BY, у вас есть 2 варианта:

Способ SQL

Выберите минимальные / максимальные значения в подзапросе, затем присоедините их исходные строки обратно (но таким образом вы должны иметь дело с тем фактом, что минимальные / максимальные значения столбцов обычно не уникальны):

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

DISTINCT ON PostgreSQL

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.

person pozs    schedule 01.12.2014
comment
Тем не менее, я не могу понять, как получить значение первой и последней даты в предложении GROUP BY. И далее связанные ставки с этими датами. - person Gening D.; 01.12.2014
comment
@GeningD. используйте min() & max() в датах. Что вы хотите делать со ставками? В вашем примере есть 3 входных значения скорости, где у вас есть одна выходная строка. - person pozs; 01.12.2014
comment
Я обновил вопрос, извините, я не продумал. - person Gening D.; 01.12.2014
comment
@GeningD. Я предположил, что rate не зависит от date (в ваших примерах они оба постоянно увеличиваются) - если это общее правило, вы также можете использовать min () и max () с rate - person pozs; 01.12.2014
comment
Большой! Большое спасибо, я потратил около 4 часов, пытаясь понять, как это сделать :) - person Gening D.; 01.12.2014

Оконные функции для этого не подходят. Вместо этого используйте агрегатные функции.

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);
person Mike Sherrill 'Cat Recall'    schedule 01.12.2014