Получать значения из первой и последней строки для каждой группы
Я новичок в 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, отображающий все.