Получать значения из первой и последней строки для каждой группы

Я новичок в Postgres, исходя из MySQL и надеясь, что один из y'all сможет мне помочь.

У меня есть таблица с тремя столбцами: name, week и value. В этой таблице записаны имена, неделя, на которой они записывали высоту, и значение их высоты. Что-то вроде этого:

Name  |  Week  | Value
------+--------+-------
John  |  1     | 9
Cassie|  2     | 5
Luke  |  6     | 3
John  |  8     | 14
Cassie|  5     | 7
Luke  |  9     | 5
John  |  2     | 10
Cassie|  4     | 4
Luke  |  7     | 4

То, что я хочу, - это список для пользователя значения в минимальную неделю и максимальную неделю. Что-то вроде этого:

Name  |minWeek | Value |maxWeek | value
------+--------+-------+--------+-------
John  |  1     | 9     | 8      | 14
Cassie|  2     | 5     | 5      | 7
Luke  |  6     | 3     | 9      | 5

В Postgres я использую этот запрос:

select name, week, value
from table t
inner join(
select name, min(week) as minweek
from table
group by name)
ss on t.name = ss.name and t.week = ss.minweek
group by t.name
;

Однако я получаю сообщение об ошибке:

столбец "w.week" должен появиться в предложении GROUP BY или использоваться в агрегированной функции
Должность: 20

Это работало отлично для меня в MySQL, поэтому мне интересно, что я здесь делаю неправильно?

Ответ 1

Это немного боль, потому что Postgres обладает хорошими функциями окна first_value() и last_value(), но это не функции агрегации. Итак, вот один из способов:

select t.name, min(t.week) as minWeek, max(firstvalue) as firstvalue,
       max(t.week) as maxWeek, max(lastvalue) as lastValue
from (select t.*, first_value(value) over (partition by name order by week) as firstvalue,
             last_value(value) over (partition by name order by week) as lastvalue
      from table t
     ) t
group by t.name;

Ответ 2

Существуют различные более простые и быстрые способы.

2x DISTINCT ON

SELECT *
FROM  (
   SELECT DISTINCT ON (name)
          name, week AS first_week, value AS first_val
   FROM   tbl
   ORDER  BY name, week
   ) f
JOIN (
   SELECT DISTINCT ON (name)
          name, week AS last_week, value AS last_val
   FROM   tbl
   ORDER  BY name, week DESC
   ) l USING (name);

Или короче:

SELECT *
FROM  (SELECT DISTINCT ON (1) name, week AS first_week, value AS first_val
       FROM   tbl ORDER BY 1,2) f
JOIN  (SELECT DISTINCT ON (1) name, week AS last_week, value AS last_val
       FROM   tbl ORDER BY 1,2 DESC) l USING (name);

Простой и понятный. Также самый быстрый в моих тестах. Подробное объяснение для DISTINCT ON:

first_value() составного типа

агрегированные функции min() или max() не принимают составные типы в качестве входных данных. Вам нужно будет создавать настраиваемые функции агрегата (что не так сложно).
Но функции работают first_value() и last_value(). Основываясь на этом, мы можем разработать очень простые решения:

Простой запрос

SELECT DISTINCT ON (name)
       name, week AS first_week, value AS first_value
     ,(first_value((week, value)) OVER (PARTITION BY name
                                        ORDER BY week DESC))::text AS l
FROM   tbl t
ORDER  BY name, week;

Выходные данные имеют все данные, но значения за последнюю неделю заполняются анонимной записью. Вам могут потребоваться разложенные значения.

Разложенный результат с оппортунистическим использованием типа таблицы

Для этого нам нужен известный тип, который регистрирует типы содержащихся элементов в системе. Адаптированное определение таблицы позволит непосредственно использовать сам тип таблицы:

CREATE TABLE tbl (week int, value int, name text) -- note optimized column order

week и value.

SELECT (l).name, first_week, first_val
     , (l).week AS last_week, (l).value AS last_val
FROM (
   SELECT DISTINCT ON (name)
          week AS first_week, value AS first_val
         ,first_value(t) OVER (PARTITION BY name ORDER BY week DESC) AS l
   FROM   tbl t
   ORDER  BY name, week
   ) sub;

Разложенный результат из пользовательского типа строки

Однако, возможно, это невозможно в большинстве случаев. Просто используйте пользовательский тип от CREATE TYPE (постоянный) или от CREATE TEMP TABLE (для ad-hoc):

CREATE TEMP TABLE nv(last_week int, last_val int);  -- register composite type

SELECT name, first_week, first_val, (l).last_week, (l).last_val
FROM (
   SELECT DISTINCT ON (name)
          name, week AS first_week, value AS first_val
         ,first_value((week, value)::nv) OVER (PARTITION BY name
                                               ORDER BY week DESC) AS l
   FROM   tbl t
   ORDER  BY name, week
   ) sub;

В локальном тесте Postgres 9.3 с аналогичной таблицей из 50 тыс. строк каждый из этих запросов был значительно быстрее, чем принятый в настоящее время ответ. Тест с EXPLAIN ANALYZE.

SQL Fiddle, отображающий все.