Existují různé jednodušší a rychlejší způsoby.
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);
Nebo kratší:
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);
Jednoduché a snadno pochopitelné. Také nejrychlejší v mých starých testech. Podrobné vysvětlení pro DISTINCT ON
:
- Vybrat první řádek v každé skupině GROUP BY?
2x funkce okna, 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;
Explicitní WINDOW
klauzule pouze zkracuje kód, nemá žádný vliv na výkon.
first_value()
kompozitního typu
Agregační funkce min()
nebo max()
nepřijímejte složené typy jako vstup. Museli byste vytvořit vlastní agregační funkce (což není tak těžké).
Ale funkce okna first_value()
a last_value()
dělat . Na základě toho můžeme navrhnout jednoduchá řešení:
Jednoduchý dotaz
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;
Výstup obsahuje všechna data, ale hodnoty za poslední týden jsou nacpané do anonymního záznamu (volitelně přetypovaného na text
). Možná budete potřebovat rozložené hodnoty.
Rozložený výsledek s příležitostným použitím typu tabulky
K tomu potřebujeme známý typ kompozitu. Upravená definice tabulky by umožnila příležitostné použití samotného typu tabulky přímo:
CREATE TABLE tbl (week int, value int, name text); -- optimized column order
week
a value
na prvním místě, takže nyní můžeme třídit podle samotného typu tabulky:
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;
Výsledek rozložený z uživatelem definovaného typu řádku
To asi ve většině případů není možné. Zaregistrujte složený typ pomocí CREATE TYPE
(trvalé) nebo pomocí CREATE TEMP TABLE
(po dobu trvání relace):
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;
Vlastní agregační funkce first()
&last()
Vytvořte funkce a agregáty jednou pro databázi:
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
);
Potom:
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;
Asi nejelegantnější řešení. Rychlejší s přídavným modulem first_last_agg
poskytující implementaci C.
Porovnejte pokyny v Postgres Wiki.
Související:
- Výpočet růstu sledujících v průběhu času pro každého influencera
db<>zde hrajte (zobrazuje se vše)
Starý sqlfiddle
Každý z těchto dotazů byl podstatně rychlejší než aktuálně přijímaná odpověď v rychlém testu na tabulce s 50 000 řádky pomocí EXPLAIN ANALYZE
.
Způsobů je více. V závislosti na distribuci dat mohou být různé styly dotazů (mnohem) rychlejší. Viz:
- Optimalizujte dotaz GROUP BY pro načtení posledního řádku na uživatele