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