Il existe différents moyens plus simples et plus rapides.
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 plus court :
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 et facile à comprendre. Aussi le plus rapide dans mes anciens tests. Explication détaillée pour DISTINCT ON
:
- Sélectionner la première ligne de chaque groupe GROUP BY ?
Fonction de fenêtre 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;
La WINDOW
explicite La clause ne fait que raccourcir le code, aucun effet sur les performances.
first_value()
de type composite
Les fonctions d'agrégation min()
ou max()
n'accepte pas les types composites en entrée. Vous auriez à créer des fonctions d'agrégation personnalisées (ce qui n'est pas si difficile).
Mais la fenêtre fonctionne first_value()
et last_value()
faire . Sur cette base, nous pouvons concevoir des solutions simples :
Requête simple
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 sortie contient toutes les données, mais les valeurs de la semaine dernière sont insérées dans un enregistrement anonyme (éventuellement converti en text
). Vous aurez peut-être besoin de valeurs décomposées.
Résultat décomposé avec utilisation opportuniste du type table
Pour cela, nous avons besoin d'un type composite bien connu. Une définition de table adaptée permettrait l'utilisation opportuniste du type de table lui-même directement :
CREATE TABLE tbl (week int, value int, name text); -- optimized column order
week
et value
viennent en premier, donc maintenant nous pouvons trier par le type de tableau lui-même :
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;
Résultat décomposé du type de ligne défini par l'utilisateur
Ce n'est probablement pas possible dans la plupart des cas. Enregistrez un type composite avec CREATE TYPE
(permanent) ou avec CREATE TEMP TABLE
(pour la durée de la session) :
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;
Fonctions d'agrégation personnalisées first()
&last()
Créez des fonctions et des agrégats une fois par base de données :
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
);
Ensuite :
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;
Probablement la solution la plus élégante. Plus rapide avec le module supplémentaire first_last_agg
fournissant une implémentation C.
Comparez les instructions dans le wiki Postgres.
Connexe :
- Calculer la croissance des abonnés au fil du temps pour chaque influenceur
db<>jouez ici (tout afficher)
Ancien sqlfiddle
Chacune de ces requêtes était nettement plus rapide que la réponse actuellement acceptée dans un test rapide sur une table de 50 000 lignes avec EXPLAIN ANALYZE
.
Il y a plus de façons. Selon la distribution des données, différents styles de requête peuvent être (beaucoup) plus rapides, pour le moment. Voir :
- Optimiser la requête GROUP BY pour récupérer la dernière ligne par utilisateur