Es gibt verschiedene einfachere und schnellere Möglichkeiten.
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);
Oder kürzer:
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);
Einfach und leicht verständlich. Auch am schnellsten in meinen alten Tests. Ausführliche Erklärung für DISTINCT ON
:
- Erste Zeile in jeder GROUP BY-Gruppe auswählen?
2x Fensterfunktion, 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;
Das explizite WINDOW
-Klausel verkürzt nur den Code, keine Auswirkung auf die Leistung.
first_value()
vom zusammengesetzten Typ
Die Aggregatfunktionen min()
oder max()
Akzeptieren Sie keine zusammengesetzten Typen als Eingabe. Sie müssten benutzerdefinierte Aggregatfunktionen erstellen (was nicht so schwer ist).
Aber die Fensterfunktionen first_value()
und last_value()
tun . Darauf aufbauend können wir einfache Lösungen erarbeiten:
Einfache Abfrage
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;
Die Ausgabe enthält alle Daten, aber die Werte für die letzte Woche werden in einen anonymen Datensatz gestopft (optional in text
umgewandelt ). Möglicherweise benötigen Sie zerlegte Werte.
Zerlegtes Ergebnis mit opportunistischer Verwendung des Tabellentyps
Dafür brauchen wir einen bekannten zusammengesetzten Typ. Eine angepasste Tabellendefinition würde die opportunistische Verwendung des Tabellentyps selbst direkt ermöglichen:
CREATE TABLE tbl (week int, value int, name text); -- optimized column order
week
und value
kommen zuerst, also können wir jetzt nach dem Tabellentyp selbst sortieren:
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;
Zerlegtes Ergebnis aus benutzerdefiniertem Zeilentyp
Das ist wohl in den meisten Fällen nicht möglich. Registrieren Sie einen zusammengesetzten Typ mit CREATE TYPE
(permanent) oder mit CREATE TEMP TABLE
(für die Dauer der Sitzung):
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;
Benutzerdefinierte Aggregatfunktionen first()
&last()
Erstellen Sie Funktionen und Aggregate einmal pro Datenbank:
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
);
Dann:
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;
Wahrscheinlich die eleganteste Lösung. Schneller mit dem Zusatzmodul first_last_agg
Bereitstellen einer C-Implementierung.
Vergleichen Sie die Anweisungen im Postgres-Wiki.
Verwandte:
- Berechnung des Follower-Wachstums im Laufe der Zeit für jeden Influencer
db<>hier fummeln (zeigt alle)
Altes sqlfiddle
Jede dieser Abfragen war in einem Schnelltest auf einer Tabelle mit 50.000 Zeilen mit EXPLAIN ANALYZE
wesentlich schneller als die aktuell akzeptierte Antwort .
Es gibt mehr Möglichkeiten. Je nach Datenverteilung können verschiedene Abfragestile jedoch (viel) schneller sein. Siehe:
- Optimieren Sie die GROUP BY-Abfrage, um die neueste Zeile pro Benutzer abzurufen