Mis à jour en août 2012 avec une version encore plus simple et plus rapide.
Sur PostgreSQL cette solution est plus simple et plus rapide :
SELECT DISTINCT ON (customer)
id, customer, total
FROM purchases
ORDER BY customer, total DESC, id
Ou plus court avec des paramètres de position :
SELECT DISTINCT ON (2)
id, customer, total
FROM purchases
ORDER BY 2, 3 DESC, 1
Principaux points
-
DISTINCT ON
est une extension PostgreSQL de la norme (où seule l'option DISTINCT
dans l'ensemble SELECT
est définie).
-
DISTINCT ON
peuvent être combinés avec ORDER BY
. Les principales expressions de ORDER BY
doivent correspondre à des expressions dans DISTINCT ON
dans cet ordre, mais vous pouvez ajouter des colonnes/expressions supplémentaires pour choisir une ligne particulière dans chaque groupe de pairs. J'ai ajouté id
à ORDER BY
pour briser l'égalité :
"Choisissez la rangée avec le plus petit id
où il y en a plusieurs qui partagent le plus grand total."
-
Pour des exigences plus complexes (non nécessaires dans ce cas simple) :
-
Vous n'ont pas à inclure n'importe quelle colonne / expression utilisée dans ORDER BY
ou DISTINCT ON
dans le SELECT
liste.
-
Vous peut inclure n'importe quelle autre colonne des tables de base dans le fichier SELECT
liste. Cela permet de 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 - 9.3. Mais cette fonctionnalité existe au moins depuis la version 7.1 (= depuis toujours).
-
La première requête avec les noms de colonnes est légèrement plus rapide que la seconde avec les index positionnels. C'est à peine mesurable.
Point de repère
J'ai effectué trois tests avec PostgreSQL 9.1 sur une table réelle de 65579 lignes et des index b-tree à colonne unique sur chacune des trois colonnes concernées et j'ai pris le meilleur des 5 exécutions.
Comparaison de @OMGPonies' première requête ( A
) à ce qui précède DISTINCT ON
solution ( B
) :
-
Sélectionnez le tableau entier, ce qui donne 5958 lignes dans ce cas.
A : Temps d'exécution total : 567.218 ms
B : Temps d'exécution total : 386.673 ms
-
Condition d'utilisation WHERE customer BETWEEN x AND y
ce qui donne 1000 lignes.
A : Temps d'exécution total : 249.136 ms
B : Temps d'exécution total : 55.111 ms
-
Sélectionnez un seul client avec WHERE customer = x
.
A : Temps d'exécution total : 0,143 ms
B : Temps d'exécution total : 0,072 ms
Index
Le site parfait pour la requête ci-dessus serait un indice 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);
C'est peut-être trop spécialisé pour les applications du monde réel. Si les performances de lecture sont cruciales dans ce cas, utilisez-la, cependant. Même test répété :
-
A : Temps d'exécution total : 277.953 ms
B : Temps d'exécution total : 193.547 ms
-
A : Temps d'exécution total : 249.796 ms -- indice spécial non utilisé
B : Temps d'exécution total : 28.679 ms
-
A : Temps d'exécution total : 0,120 ms
B : Temps d'exécution total : 0,048 ms
Efficacité
Vous devez peser le coût et les avantages avant de créer un index sur mesure pour chaque requête. Le potentiel de l'index ci-dessus dépend largement distribution des données .
L'index est utilisé parce qu'il fournit des données pré-triées, et dans Postgres 9.2 ou ultérieur, la requête peut également bénéficier d'une fonction index only scan si la largeur de l'index est inférieure à celle du tableau sous-jacent. L'index doit cependant être scanné dans son intégralité.
-
Pour beaucoup de les clients avec quelques lignes chacune, cette méthode est très efficace, d'autant plus si vous avez de toute façon besoin d'une sortie triée. L'avantage diminue avec le ratio de lignes par client.
-
Pour quelques les clients avec beaucoup de rangs, l'équivalent d'un balayage d'index libre serait beaucoup plus efficace, mais ce n'est pas encore implémenté dans Postgres (jusqu'à la version 9.4).
Il existe des alternatives pour l'émuler. En particulier, si vous disposez d'une table séparée (dérivée) contenant des clients uniques (ce qui est souvent le cas), il existe les possibilités suivantes techniques d'interrogation plus rapides :
0 votes
Puisque vous ne cherchez que la plus grande, pourquoi ne pas demander
MAX(total)
?24 votes
@phil294 L'interrogation de max(total) n'associera pas ce total à la valeur 'id' de la ligne sur laquelle il s'est produit.
1 votes
Cela répond-il à votre question ? Comment sélectionner la première ligne par groupe dans une requête SQL ?
0 votes
Si quelqu'un veut
group by + join rows data
voir stackoverflow.com/questions/12558509/