Existem várias maneiras mais simples e rápidas.
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);
Ou mais curto:
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);
Simples e fácil de se entender. Também mais rápido em meus testes antigos. Explicação detalhada para
DISTINCT ON
:- Selecionar a primeira linha em cada grupo GROUP BY?
2x função de janela, 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;
A
WINDOW
explícita cláusula apenas encurta o código, sem afetar o desempenho. first_value()
do tipo composto
As funções agregadas
min()
ou max()
não aceite tipos compostos como entrada. Você teria que criar funções de agregação personalizadas (o que não é tão difícil).Mas as funções de janela
first_value()
e last_value()
faça . Com base nisso, podemos conceber soluções simples:Consulta simples
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;
A saída tem todos os dados, mas os valores da última semana são colocados em um registro anônimo (opcionalmente convertido para
text
). Você pode precisar de valores decompostos. Resultado decomposto com uso oportunista do tipo de tabela
Para isso, precisamos de um tipo composto bem conhecido. Uma definição de tabela adaptada permitiria o uso oportunista do próprio tipo de tabela diretamente:
CREATE TABLE tbl (week int, value int, name text); -- optimized column order
week
e value
vem primeiro, então agora podemos classificar pelo próprio tipo de tabela: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;
Resultado decomposto do tipo de linha definido pelo usuário
Isso provavelmente não é possível na maioria dos casos. Registre um tipo composto com
CREATE TYPE
(permanente) ou com CREATE TEMP TABLE
(durante a sessão):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;
Funções de agregação personalizadas first()
&last()
Crie funções e agregações uma vez por banco de dados:
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
);
Então:
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;
Provavelmente a solução mais elegante. Mais rápido com o módulo adicional
first_last_agg
fornecendo uma implementação em C.Compare as instruções no Postgres Wiki.
Relacionado:
- Calculando o crescimento de seguidores ao longo do tempo para cada influenciador
db<>mexa aqui (mostrando tudo)
antigo sqlfiddle
Cada uma dessas consultas foi substancialmente mais rápida do que a resposta atualmente aceita em um teste rápido em uma tabela com 50 mil linhas com
EXPLAIN ANALYZE
. Existem mais maneiras. Dependendo da distribuição de dados, diferentes estilos de consulta podem ser (muito) mais rápidos. Ver:
- Otimize a consulta GROUP BY para recuperar a última linha por usuário