Er zijn verschillende eenvoudigere en snellere manieren.
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);
Of korter:
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);
Eenvoudig en gemakkelijk te begrijpen. Ook de snelste in mijn oude tests. Gedetailleerde uitleg voor DISTINCT ON
:
- Selecteer de eerste rij in elke GROUP BY-groep?
2x vensterfunctie, 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;
Het expliciete WINDOW
clausule verkort alleen de code, geen effect op de prestaties.
first_value()
van composiettype
De aggregatiefuncties min()
of max()
accepteer geen samengestelde typen als invoer. U zou aangepaste statistische functies moeten maken (wat niet zo moeilijk is).
Maar de vensterfuncties first_value()
en last_value()
doen . Op basis daarvan kunnen we eenvoudige oplossingen bedenken:
Eenvoudige vraag
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;
De uitvoer heeft alle gegevens, maar de waarden voor de afgelopen week zijn in een anoniem record gestopt (optioneel naar text
gecast ). Mogelijk hebt u ontlede waarden nodig.
Ontleed resultaat met opportunistisch gebruik van tabeltype
Daarvoor hebben we een bekend composiettype nodig. Een aangepaste tabeldefinitie zou het opportunistische gebruik van het tafeltype zelf rechtstreeks mogelijk maken:
CREATE TABLE tbl (week int, value int, name text); -- optimized column order
week
en value
komen eerst, dus nu kunnen we sorteren op het tabeltype zelf:
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;
Ontbonden resultaat van door de gebruiker gedefinieerd rijtype
Dat is in de meeste gevallen waarschijnlijk niet mogelijk. Registreer een samengesteld type met CREATE TYPE
(permanent) of met CREATE TEMP TABLE
(voor de duur van de sessie):
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;
Aangepaste statistische functies first()
&last()
Maak één keer per database functies en aggregaties aan:
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
);
Dan:
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;
Waarschijnlijk de meest elegante oplossing. Sneller met de extra module first_last_agg
het verstrekken van een C-implementatie.
Vergelijk instructies in de Postgres Wiki.
Gerelateerd:
- De groei van het aantal volgers in de loop van de tijd berekenen voor elke influencer
db<>viool hier (laat alles zien)
Oude sqlfiddle
Elk van deze vragen was aanzienlijk sneller dan het momenteel geaccepteerde antwoord in een snelle test op een tafel met 50.000 rijen met EXPLAIN ANALYZE
.
Er zijn meer manieren. Afhankelijk van de gegevensdistributie kunnen verschillende zoekstijlen (veel) sneller zijn, maar toch. Zie:
- Optimaliseer de GROUP BY-query om de laatste rij per gebruiker op te halen