PostgreSQL
 sql >> база данни >  >> RDS >> PostgreSQL

Вземете стойности от първия и последния ред за група

Има различни по-прости и бързи начини.

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 :

  • Изберете ли първия ред във всяка група GROUP BY?

2x функция прозорец, 1x DISTINCT ON

SELECT DISTINCT ON (name)
       name, week AS first_week, value AS first_val
     , first_value(week)  OVER w AS last_week
     , first_value(value) OVER w AS last_value
FROM   tbl t
WINDOW w AS (PARTITION BY name ORDER BY week DESC)
ORDER  BY name, week;

Изричният WINDOW клаузата само съкращава кода, без ефект върху производителността.

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;

Резултатът съдържа всички данни, но стойностите за последната седмица са напълнени в анонимен запис (по избор се прехвърля към text ). Може да имате нужда от разложени стойности.

Разложен резултат с опортюнистично използване на тип таблица

За това се нуждаем от добре познат композитен тип. Адаптирана дефиниция на таблица би позволила опортюнистично използване директно на самия тип таблица:

CREATE TABLE tbl (week int, value int, name text);  -- 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 (за продължителността на сесията):

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;

Персонализирани агрегатни функции first() &last()

Създавайте функции и агрегати веднъж на база данни:

CREATE OR REPLACE FUNCTION public.first_agg (anyelement, anyelement)
  RETURNS anyelement
  LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS
'SELECT $1;'

CREATE AGGREGATE public.first(anyelement) (
  SFUNC = public.first_agg
, STYPE = anyelement
, PARALLEL = safe
);


CREATE OR REPLACE FUNCTION public.last_agg (anyelement, anyelement)
  RETURNS anyelement
  LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS
'SELECT $2';

CREATE AGGREGATE public.last(anyelement) (
  SFUNC = public.last_agg
, STYPE = anyelement
, PARALLEL = safe
);

След това:

SELECT name
     , first(week) AS first_week, first(value) AS first_val
     , last(week)  AS last_week , last(value)  AS last_val
FROM  (SELECT * FROM tbl ORDER BY name, week) t
GROUP  BY name;

Може би най-елегантното решение. По-бързо с допълнителния модул first_last_agg предоставяне на C реализация.
Сравнете инструкциите в Postgres Wiki.

Свързано:

  • Изчисляване на растежа на последователите във времето за всеки влиятелен човек

db<>цигулка тук (показват се всички)
Стар sqlfiddle

Всяка от тези заявки беше значително по-бърза от приетия в момента отговор в бърз тест на таблица с 50 000 реда с EXPLAIN ANALYZE .

Има повече начини. В зависимост от разпределението на данните, различните стилове на заявка може да са (много) по-бързи, но все пак. Вижте:

  • Оптимизирайте заявката GROUP BY, за да извлечете последния ред на потребител


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Дълбоко гмуркане на доставчик на облак:PostgreSQL на DigitalOcean

  2. Cloud Vendor Deep-Dive:PostgreSQL на Google Cloud Platform (GCP)

  3. персонализирайте пейджър в psql

  4. Как работи COPY и защо е толкова по-бързо от INSERT?

  5. Осмисляне на размерите на редовете в Postgres