Има различни по-прости и бързи начини.
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);
Или по-кратко:
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);
Прост и лесен за разбиране. Също така най-бърз в старите ми тестове. Подробно обяснение за DISTINCT ON
:
- Изберете ли първия ред във всяка група GROUP BY?
2x функция прозорец, 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;
Изричният WINDOW
клаузата само съкращава кода, без ефект върху производителността.
first_value()
от композитен тип
Агрегатните функции min()
или max()
не приемайте съставни типове като вход. Ще трябва да създадете персонализирани агрегатни функции (което не е толкова трудно).
Но функциите на прозореца first_value()
и last_value()
направи . Въз основа на това можем да измислим прости решения:
Проста заявка
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;
Резултатът съдържа всички данни, но стойностите за последната седмица са напълнени в анонимен запис (по избор се прехвърля към text
). Може да имате нужда от разложени стойности.
Разложен резултат с опортюнистично използване на тип таблица
За това се нуждаем от добре познат композитен тип. Адаптирана дефиниция на таблица би позволила опортюнистично използване директно на самия тип таблица:
CREATE TABLE tbl (week int, value int, name text); -- optimized column order
week
и value
идват първи, така че сега можем да сортираме по самия тип таблица:
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;
Разложен резултат от дефиниран от потребителя тип ред
Това вероятно не е възможно в повечето случаи. Регистрирайте съставен тип с CREATE TYPE
(постоянно) или с CREATE TEMP TABLE
(за продължителността на сесията):
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;
Персонализирани агрегатни функции first()
&last()
Създавайте функции и агрегати веднъж на база данни:
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
);
След това:
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;
Може би най-елегантното решение. По-бързо с допълнителния модул first_last_agg
предоставяне на C реализация.
Сравнете инструкциите в Postgres Wiki.
Свързано:
- Изчисляване на растежа на последователите във времето за всеки влиятелен човек
db<>цигулка тук (показват се всички)
Стар sqlfiddle
Всяка от тези заявки беше значително по-бърза от приетия в момента отговор в бърз тест на таблица с 50 000 реда с EXPLAIN ANALYZE
.
Има повече начини. В зависимост от разпределението на данните, различните стилове на заявка може да са (много) по-бързи, но все пак. Вижте:
- Оптимизирайте заявката GROUP BY, за да извлечете последния ред на потребител