Der er forskellige enklere og hurtigere måder.
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 kortere:
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);
Enkel og let at forstå. Også hurtigst i mine gamle tests. Detaljeret forklaring på DISTINCT ON
:
- Vælg første række i hver GROUP BY-gruppe?
2x vinduesfunktion, 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 eksplicitte WINDOW
klausul forkorter kun koden, ingen effekt på ydeevnen.
first_value()
af sammensat type
De samlede funktioner min()
eller max()
accepterer ikke sammensatte typer som input. Du bliver nødt til at oprette tilpassede aggregerede funktioner (hvilket ikke er så svært).
Men vinduesfunktionerne first_value()
og last_value()
gør . Med udgangspunkt i det kan vi udtænke simple løsninger:
Simpel forespørgsel
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;
Outputtet har alle data, men værdierne for den sidste uge er fyldt i en anonym post (valgfrit castet til text
). Du har muligvis brug for dekomponerede værdier.
Dekomponeret resultat med opportunistisk brug af tabeltype
Til det har vi brug for en velkendt komposittype. En tilpasset tabeldefinition ville give mulighed for opportunistisk brug af selve tabeltypen direkte:
CREATE TABLE tbl (week int, value int, name text); -- optimized column order
week
og value
kom først, så nu kan vi sortere efter selve tabeltypen:
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;
Dekomponeret resultat fra brugerdefineret rækketype
Det er nok ikke muligt i de fleste tilfælde. Registrer en sammensat type med CREATE TYPE
(permanent) eller med CREATE TEMP TABLE
(i løbet af sessionen):
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;
Tilpassede aggregerede funktioner first()
&last()
Opret funktioner og aggregater én gang pr. database:
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
);
Så:
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;
Nok den mest elegante løsning. Hurtigere med det ekstra modul first_last_agg
leverer en C-implementering.
Sammenlign instruktionerne i Postgres Wiki.
Relateret:
- Beregning af følgervækst over tid for hver influencer
db<>spil her (viser alle)
Gamle sqlfiddle
Hver af disse forespørgsler var væsentligt hurtigere end det aktuelt accepterede svar i en hurtig test på en tabel med 50.000 rækker med EXPLAIN ANALYZE
.
Der er flere måder. Afhængigt af datadistribution kan forskellige forespørgselsstile være (meget) hurtigere, men alligevel. Se:
- Optimer GROUP BY-forespørgsel for at hente seneste række pr. bruger