PostgreSQL
 sql >> Baza danych >  >> RDS >> PostgreSQL

Uzyskaj wartości z pierwszego i ostatniego wiersza na grupę

Istnieje wiele prostszych i szybszych sposobów.

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);

Lub krócej:

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);

Prosty i łatwy do zrozumienia. Również najszybszy w moich starych testach. Szczegółowe wyjaśnienie dla DISTINCT ON :

  • Wybrać pierwszy wiersz w każdej grupie GROUP BY?

2x funkcja 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;

Wyraźne WINDOW klauzula tylko skraca kod, nie ma wpływu na wydajność.

first_value() typu kompozytowego

Funkcje agregujące min() lub max() nie akceptuj typów złożonych jako danych wejściowych. Musiałbyś stworzyć niestandardowe funkcje agregujące (co nie jest takie trudne).
Ale funkcje okna first_value() i last_value() zrobić . Bazując na tym możemy opracować proste rozwiązania:

Proste zapytanie

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;

Dane wyjściowe zawierają wszystkie dane, ale wartości z ostatniego tygodnia są umieszczane w anonimowym rekordzie (opcjonalnie rzutowane na text ). Możesz potrzebować wartości rozłożonych.

Wynik rozłożony z oportunistycznym wykorzystaniem typu tabeli

Do tego potrzebujemy dobrze znanego typu kompozytu. Dostosowana definicja tabeli pozwoliłaby na oportunistyczne wykorzystanie samego typu tabeli bezpośrednio:

CREATE TABLE tbl (week int, value int, name text);  -- optimized column order

week i value pierwszeństwo, więc teraz możemy sortować według samego typu tabeli:

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;

Rozłożony wynik z typu wiersza zdefiniowanego przez użytkownika

To prawdopodobnie nie jest możliwe w większości przypadków. Zarejestruj typ złożony za pomocą CREATE TYPE (na stałe) lub za pomocą CREATE TEMP TABLE (na czas trwania sesji):

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;

Niestandardowe funkcje agregujące first() &last()

Utwórz funkcje i agregacje raz na bazę danych:

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
);

Następnie:

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;

Chyba najbardziej eleganckie rozwiązanie. Szybciej z dodatkowym modułem first_last_agg dostarczanie implementacji C.
Porównaj instrukcje w Postgres Wiki.

Powiązane:

  • Obliczanie wzrostu liczby obserwujących w czasie dla każdego influencera

db<>graj tutaj (pokazuje wszystkie)
Stary sqlfiddle

Każde z tych zapytań było znacznie szybsze niż obecnie akceptowana odpowiedź w szybkim teście na tabeli z 50 tys. wierszy z EXPLAIN ANALYZE .

Jest więcej sposobów. W zależności od dystrybucji danych różne style zapytań mogą być (znacznie) szybsze. Zobacz:

  • Zoptymalizuj zapytanie GROUP BY, aby pobrać ostatni wiersz na użytkownika


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Najlepszy sposób na liczenie rekordów w dowolnych odstępach czasu w Rails+Postgres

  2. postgresql:INSERT INTO ... (WYBIERZ * ...)

  3. Różnica między LIKE i ~ w Postgres

  4. Jak korzystać z LoggingConnection firmy Psycopg2?

  5. Mieszanie sprzężeń jawnych i niejawnych kończy się niepowodzeniem z Istnieje wpis dla tabeli ... ale nie można się do niego odwoływać z tej części zapytania