Get values from first and last row per group

僤鯓⒐⒋嵵緔 提交于 2019-11-29 09:56:37

This is a bit of a pain, because Postgres has the nice window functions first_value() and last_value(), but these are not aggregation functions. So, here is one way:

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

There are various simpler and faster ways.

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

Or shorter:

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

Simple and easy to understand. Also fastest in my tests. Detailed explanation for DISTINCT ON:

first_value() of composite type

The aggregate functions min() or max() do not accept composite types as input. You would have to create custom aggregate functions (which is not that hard).
But the window functions first_value() and last_value() do. Building on that we can devise an very simple solutions:

Simple query

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;

The output has all data, but the values for the last week are stuffed into an anonymous record. You may need decomposed values.

Decomposed result with opportunistic use of table type

For that we need a well-known type that registers the types of contained elements with the system. An adapted table definition would allow for the opportunistic use of the table type itself directly:

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

weekand value come first.

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;

Decomposed result from user-defined row type

However, that's probably not possible in most cases. Just use a user-defined type from CREATE TYPE (permanent) or from CREATE TEMP TABLE (for ad-hoc use):

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;

In a local test on Postgres 9.3 with a similar table of 50k rows, each of these queries was substantially faster than the currently accepted answer. Test with EXPLAIN ANALYZE.

SQL Fiddle displaying all.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!