Esistono vari modi più semplici e veloci.
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);
O più breve:
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);
Semplice e facile da capire. Anche più veloce nei miei vecchi test. Spiegazione dettagliata per DISTINCT ON
:
- Seleziona la prima riga in ogni gruppo GROUP BY?
2x funzione finestra, 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;
La WINDOW
esplicita clausola accorcia solo il codice, nessun effetto sulle prestazioni.
first_value()
di tipo composito
Le funzioni aggregate min()
o max()
non accettare tipi compositi come input. Dovresti creare funzioni aggregate personalizzate (che non è così difficile).
Ma le funzioni della finestra first_value()
e last_value()
fai . Partendo da ciò possiamo escogitare soluzioni semplici:
Richiesta semplice
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;
L'output contiene tutti i dati, ma i valori dell'ultima settimana vengono inseriti in un record anonimo (facoltativamente trasmesso su text
). Potresti aver bisogno di valori scomposti.
Risultato scomposto con uso opportunistico del tipo di tabella
Per questo abbiamo bisogno di un noto tipo composito. Una definizione di tabella adattata consentirebbe l'uso opportunistico del tipo di tabella stesso direttamente:
CREATE TABLE tbl (week int, value int, name text); -- optimized column order
week
e value
vieni prima, quindi ora possiamo ordinare in base al tipo di tabella stesso:
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;
Risultato scomposto dal tipo di riga definito dall'utente
Probabilmente non è possibile nella maggior parte dei casi. Registra un tipo composto con CREATE TYPE
(permanente) o con CREATE TEMP TABLE
(per la durata della sessione):
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;
Funzioni aggregate personalizzate first()
&last()
Crea funzioni e aggregati una volta per 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
);
Quindi:
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;
Probabilmente la soluzione più elegante. Più veloce con il modulo aggiuntivo first_last_agg
fornendo un'implementazione C.
Confronta le istruzioni nel Wiki di Postgres.
Correlati:
- Calcolo della crescita dei follower nel tempo per ogni influencer
db<>gioca qui (mostra tutto)
Sqlfiddle vecchio
Ognuna di queste domande è stata sostanzialmente più veloce della risposta attualmente accettata in un rapido test su una tabella con 50.000 righe con EXPLAIN ANALYZE
.
Ci sono più modi. A seconda della distribuzione dei dati, stili di query diversi potrebbero essere (molto) più veloci. Vedi:
- Ottimizza la query GROUP BY per recuperare l'ultima riga per utente