Détails
Comment puis-je (ou puis-je) effectuer un SELECT DISTINCT sur plusieurs colonnes ?
J'ai besoin de récupérer toutes les lignes d'un tableau dont les deux colonnes combinées sont toutes différentes. Je veux donc toutes les ventes qui n'ont pas d'autres ventes qui ont eu lieu le même jour pour le même prix. Les ventes qui sont uniques en fonction du jour et du prix seront mises à jour et auront un statut actif.
Je pense donc :
UPDATE sales
SET status = 'ACTIVE'
WHERE id IN (SELECT DISTINCT (saleprice, saledate), id, count(id)
FROM sales
HAVING count = 1)
Mais mon cerveau a du mal à aller plus loin que ça.
382
3
est très équivalent à :
Il est bon de se familiariser avec la syntaxe GROUP BY, car elle est plus puissante.
Pour votre requête, je procéderais comme suit :
Si vous rassemblez les réponses obtenues jusqu'à présent, que vous les nettoyez et les améliorez, vous obtiendrez cette question supérieure :
Qui est beaucoup plus rapide que l'une ou l'autre. Les performances de la réponse actuellement acceptée sont multipliées par 10 à 15 (dans mes tests sur PostgreSQL 8.4 et 9.1).
Mais c'est encore loin d'être optimal. Utilisez une [
NOT EXISTS
][1] (anti-)semi-jonction pour des performances encore meilleures. EXISTS` est du SQL standard, existe depuis toujours (au moins depuis PostgreSQL 7.2, bien avant que cette question ne soit posée) et répond parfaitement aux exigences présentées :dbfiddle here [Old SQL Fiddle][2]
Clé unique pour identifier la ligne
Si vous n'avez pas de clé primaire ou unique pour la table (
id
dans l'exemple), vous pouvez la remplacer par la colonne systèmectid
pour les besoins de cette requête (mais pas pour d'autres besoins) :Chaque table doit avoir une clé primaire. Ajoutez-en une si vous n'en avez pas encore. Je suggère une colonne
serial
ouIDENTITY
dans Postgres 10+.Relié :
Comment est-ce plus rapide ?
La sous-requête dans l'anti-semi-joint
EXISTS
peut arrêter de s'évaluer dès que le premier doublon est trouvé (inutile de chercher plus loin). Pour une table de base avec peu de doublons, cela n'est que légèrement plus efficace. Avec beaucoup de doublons, cela devient bien plus efficace.Exclure les mises à jour vides
Pour les lignes qui ont déjà
status = 'ACTIVE'
cette mise à jour ne changerait rien, mais insérerait quand même une nouvelle version de ligne au coût total (des exceptions mineures s'appliquent). Normalement, ce n'est pas ce que vous voulez. Ajoutez une autre conditionWHERE
comme démontré ci-dessus pour éviter cela et rendre le processus encore plus rapide :Si
status
est définiNOT NULL
, vous pouvez simplifier à :Subtile différence dans la gestion de NULL
Cette requête (contrairement à la réponse actuellement acceptée par Joel) ne traite pas les valeurs NULL comme égales. Les deux lignes suivantes pour
(saleprice, saledate)
seraient qualifiées de "distinctes" ; (bien qu'elles semblent identiques à l'oeil humain) :Passe aussi dans un index unique et presque partout ailleurs, puisque les valeurs NULL ne se comparent pas égales selon la norme SQL. Voir :
En revanche,
GROUP BY
,DISTINCT
ouDISTINCT ON ()
traitent les valeurs NULL comme égales. Utilisez un style de requête approprié en fonction de ce que vous voulez obtenir. Vous pouvez toujours utiliser cette requête plus rapide avec [IS NOT DISTINCT FROM
][4] au lieu de=
pour une ou toutes les comparaisons afin de rendre égales les valeurs NULL. Plus encore :Si toutes les colonnes comparées sont définies
NOT NULL
, il n'y a pas de place pour le désaccord.[1] : https://www.postgresql.org/docs/current/functions-subquery.html#FUNCTIONS-SUBQUERY-EXISTS [2] : http://sqlfiddle.com/#!17/6b5ef/1
[4] : https://www.postgresql.org/docs/current/functions-comparison.html#FUNCTIONS-COMPARISON-PRED-TABLE
Le problème de votre requête est que lorsque vous utilisez une clause GROUP BY (ce que vous faites essentiellement en utilisant distinct), vous ne pouvez utiliser que les colonnes que vous regroupez par ou les fonctions d'agrégation. Vous ne pouvez pas utiliser la colonne id car il y a potentiellement des valeurs différentes. Dans votre cas, il n'y a toujours qu'une seule valeur en raison de la clause HAVING, mais la plupart des SGBDR ne sont pas assez intelligents pour le reconnaître.
Cela devrait toutefois fonctionner (et ne nécessite pas de jointure) :
Vous pourriez également utiliser MAX ou AVG au lieu de MIN. Il est seulement important d'utiliser une fonction qui renvoie la valeur de la colonne s'il n'y a qu'une seule ligne correspondante.