Hay varias formas más simples y 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);
O más corto:
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);
Simple y fácil de entender. También más rápido en mis pruebas anteriores. Explicación detallada de DISTINCT ON
:
- ¿Seleccionar la primera fila en cada grupo GROUP BY?
2x función de ventana, 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;
La WINDOW
explícita La cláusula solo acorta el código, no afecta el rendimiento.
first_value()
de tipo compuesto
Las funciones agregadas min()
o max()
no acepte tipos compuestos como entrada. Tendría que crear funciones agregadas personalizadas (que no es tan difícil).
Pero las funciones de ventana first_value()
y last_value()
hacer . Basándonos en eso, podemos idear soluciones simples:
Consulta sencilla
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;
La salida tiene todos los datos, pero los valores de la última semana se introducen en un registro anónimo (opcionalmente convertido a text
) ). Es posible que necesite valores descompuestos.
Resultado descompuesto con uso oportunista del tipo de tabla
Para eso necesitamos un tipo compuesto bien conocido. Una definición de tabla adaptada permitiría el uso oportunista del tipo de tabla directamente:
CREATE TABLE tbl (week int, value int, name text); -- optimized column order
week
y value
vienen primero, así que ahora podemos ordenar por el tipo de tabla en sí:
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 descompuesto del tipo de fila definido por el usuario
Eso probablemente no sea posible en la mayoría de los casos. Registre un tipo compuesto con CREATE TYPE
(permanente) o con CREATE TEMP TABLE
(durante la duración de la sesión):
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;
Funciones agregadas personalizadas first()
&last()
Cree funciones y agregados una vez por base de datos:
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
);
Entonces:
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;
Probablemente la solución más elegante. Más rápido con el módulo adicional first_last_agg
proporcionando una implementación en C.
Compare las instrucciones en Postgres Wiki.
Relacionado:
- Calcular el crecimiento de seguidores a lo largo del tiempo para cada influencer
db<>violín aquí (mostrando todo)
Sqlfiddle antiguo
Cada una de estas consultas fue sustancialmente más rápida que la respuesta actualmente aceptada en una prueba rápida en una tabla con 50k filas con EXPLAIN ANALYZE
.
Hay más formas. Dependiendo de la distribución de datos, los diferentes estilos de consulta pueden ser (mucho) más rápidos aún. Ver:
- Optimizar la consulta GROUP BY para recuperar la fila más reciente por usuario