sql >> Databasteknik >  >> RDS >> PostgreSQL

Hämta värden från första och sista raden per grupp

Det finns olika enklare och snabbare sätt.

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

Eller kortare:

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

Enkelt och lätt att förstå. Också snabbast i mina gamla test. Detaljerad förklaring för DISTINCT ON :

  • Välj första raden i varje GROUP BY-grupp?

2x fönsterfunktion, 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;

Det explicita WINDOW klausul förkortar bara koden, ingen effekt på prestandan.

first_value() av sammansatt typ

De aggregerade funktionerna min() eller max() accepterar inte sammansatta typer som indata. Du måste skapa anpassade aggregerade funktioner (vilket inte är så svårt).
Men fönsterfunktionerna first_value() och last_value() gör . Utifrån det kan vi ta fram enkla lösningar:

Enkel fråga

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;

Utdata har all data, men värdena för den senaste veckan stoppas in i en anonym post (valfritt cast till text ). Du kan behöva nedbrutna värden.

Dekomponerat resultat med opportunistisk användning av tabelltyp

Till det behöver vi en välkänd komposittyp. En anpassad tabelldefinition skulle möjliggöra opportunistisk användning av själva tabelltypen direkt:

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

week och value kom först, så nu kan vi sortera efter själva tabelltypen:

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;

Dekomponerat resultat från användardefinierad radtyp

Det är nog inte möjligt i de flesta fall. Registrera en sammansatt typ med CREATE TYPE (permanent) eller med CREATE TEMP TABLE (under sessionens varaktighet):

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;

Anpassade aggregerade funktioner first() &last()

Skapa funktioner och aggregat en gång per databas:

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

Sedan:

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;

Förmodligen den mest eleganta lösningen. Snabbare med tilläggsmodulen first_last_agg tillhandahåller en C-implementering.
Jämför instruktioner i Postgres Wiki.

Relaterat:

  • Beräkna följartillväxt över tid för varje influencer

db<>spela här (visar alla)
Gammal sqlfiddle

Var och en av dessa frågor var avsevärt snabbare än det för närvarande accepterade svaret i ett snabbtest på en tabell med 50 000 rader med EXPLAIN ANALYZE .

Det finns fler sätt. Beroende på datadistribution kan olika frågestilar vara (mycket) snabbare, men ändå. Se:

  • Optimera GROUP BY-frågan för att hämta den senaste raden per användare


  1. MySQL:Hur man tillåter fjärranslutning till mysql

  2. ROUND() Exempel i SQL Server

  3. Dela upp stora raderingsoperationer i bitar

  4. Bästa sättet att förkorta UTF8-strängen baserat på bytelängd