sql >> Database >  >> RDS >> PostgreSQL

Krijg waarden van de eerste en laatste rij per groep

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


  1. HHVM gebruiken met WordPress

  2. Hoe TRIM_ORACLE() werkt in MariaDB

  3. Wat is het verschil tussen 'YYYY' en 'RRRR' in Oracle SQL?

  4. Waarden krijgen die geen getallen bevatten in MariaDB