PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Rufen Sie Werte aus der ersten und letzten Zeile pro Gruppe ab

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