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