sql >> Databáze >  >> RDS >> PostgreSQL

Získejte hodnoty z prvního a posledního řádku na skupinu

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


  1. Jak funguje SQLite Avg()

  2. Připojení k místní databázi SQL Server pomocí C#

  3. Nativní dotaz hibernace – sloupec char(3).

  4. Mohu parametrizovat název tabulky v připraveném příkazu?