DISTINCT ON
est généralement le plus simple et le plus rapide pour cela dans PostgreSQL .
(Pour l'optimisation des performances pour certaines charges de travail, voir ci-dessous.)
SELECT DISTINCT ON (customer)
id, customer, total
FROM purchases
ORDER BY customer, total DESC, id;
Ou plus court (sinon aussi clair) avec des nombres ordinaux de colonnes de sortie :
SELECT DISTINCT ON (2)
id, customer, total
FROM purchases
ORDER BY 2, 3 DESC, 1;
Si total
peut être NULL (cela ne fera pas de mal de toute façon, mais vous voudrez faire correspondre les index existants) :
...
ORDER BY customer, total DESC NULLS LAST, id;
Points majeurs
DISTINCT ON
est une extension PostgreSQL du standard (où seul DISTINCT
dans l'ensemble SELECT
liste est définie).
Lister n'importe quel nombre d'expressions dans le DISTINCT ON
clause, la valeur de ligne combinée définit les doublons. Le manuel :
Évidemment, deux lignes sont considérées comme distinctes si elles diffèrent par au moins une valeur de colonne. Les valeurs nulles sont considérées comme égales dans cette comparaison.
J'insiste sur moi.
DISTINCT ON
peut être combiné avec ORDER BY
. Expressions principales dans ORDER BY
doit être dans l'ensemble d'expressions dans DISTINCT ON
, mais vous pouvez réorganiser librement l'ordre parmi ceux-ci. Exemple.
Vous pouvez ajouter des éléments supplémentaires expressions à ORDER BY
pour choisir une ligne particulière dans chaque groupe de pairs. Ou, comme le dit le manuel :
Le
DISTINCT ON
les expressions doivent correspondre auORDER BY
le plus à gauche expressions). LeORDER BY
la clause contiendra normalement des expressions supplémentaires qui déterminent la priorité souhaitée des lignes dans chaqueDISTINCT ON
groupe.
J'ai ajouté id
comme dernier élément à départager :
"Choisissez la ligne avec le plus petit id
de chaque groupe partageant le total
le plus élevé ."
Pour trier les résultats d'une manière qui n'est pas d'accord avec l'ordre de tri déterminant le premier par groupe, vous pouvez imbriquer la requête ci-dessus dans une requête externe avec un autre ORDER BY
. Exemple.
Si total
peut être NULL, vous probablement voulez la ligne avec la plus grande valeur non nulle. Ajouter NULLS LAST
comme démontré. Voir :
- Trier par colonne ASC, mais les valeurs NULL en premier ?
Le SELECT
liste n'est pas contraint par des expressions dans DISTINCT ON
ou ORDER BY
de quelque manière que. (Non nécessaire dans le cas simple ci-dessus) :
-
Vous n'êtes pas obligé inclure l'une des expressions dans
DISTINCT ON
ouORDER BY
. -
Vous pouvez inclure toute autre expression dans le
SELECT
liste. Ceci est essentiel pour remplacer des requêtes beaucoup plus complexes par des sous-requêtes et des fonctions d'agrégation/fenêtre.
J'ai testé avec les versions 8.3 à 13 de Postgres. Mais la fonctionnalité existe au moins depuis la version 7.1, donc en gros toujours.
Index
Le parfait l'index pour la requête ci-dessus serait un index multi-colonnes couvrant les trois colonnes dans l'ordre correspondant et avec l'ordre de tri correspondant :
CREATE INDEX purchases_3c_idx ON purchases (customer, total DESC, id);
Peut-être trop spécialisé. Mais utilisez-le si les performances de lecture pour la requête particulière sont cruciales. Si vous avez DESC NULLS LAST
dans la requête, utilisez la même chose dans l'index afin que l'ordre de tri corresponde et que l'index soit applicable.
Efficacité / Optimisation des performances
Évaluez les coûts et les avantages avant de créer des index personnalisés pour chaque requête. Le potentiel de l'indice ci-dessus dépend en grande partie de la distribution des données .
L'index est utilisé car il fournit des données pré-triées. Dans Postgres 9.2 ou version ultérieure, la requête peut également bénéficier d'un balayage d'index uniquement si l'index est plus petit que la table sous-jacente. L'index doit cependant être scanné dans son intégralité.
Pour peu lignes par client (cardinalité élevée dans la colonne customer
), c'est très efficace. Encore plus si vous avez de toute façon besoin d'une sortie triée. L'avantage diminue avec un nombre croissant de lignes par client.
Idéalement, vous avez suffisamment de work_mem
pour traiter l'étape de tri impliquée dans la RAM et ne pas se répandre sur le disque. Mais généralement en définissant work_mem
aussi élevée peut avoir des effets indésirables. Considérez SET LOCAL
pour les requêtes exceptionnellement volumineuses. Trouvez combien vous avez besoin avec EXPLAIN ANALYZE
. Mention de "Disque : " dans l'étape de tri indique le besoin d'en savoir plus :
- Paramètre de configuration work_mem dans PostgreSQL sous Linux
- Optimiser les requêtes simples à l'aide de la date et du texte ORDER BY
Pour beaucoup lignes par client (faible cardinalité dans la colonne customer
), une analyse d'index lâche (alias "ignorer l'analyse") serait (beaucoup) plus efficace, mais cela n'est pas implémenté jusqu'à Postgres 14. (Une implémentation pour les analyses d'index uniquement est en cours de développement pour Postgres 15. Voir ici et ici.)
Pour maintenant, il existe des techniques de requête plus rapides pour se substituer à cela. En particulier si vous avez une table séparée contenant des clients uniques, ce qui est le cas d'utilisation typique. Mais aussi si vous ne le faites pas :
- SELECT DISTINCT est plus lent que prévu sur ma table dans PostgreSQL
- Optimiser la requête GROUP BY pour récupérer la dernière ligne par utilisateur
- Optimiser la requête maximale par groupe
- Interroger les N dernières lignes associées par ligne
Repères
Voir réponse séparée.