DISTINCT pour une seule colonne


156

Disons que j'ai la requête suivante.

SELECT ID, Email, ProductName, ProductModel FROM Products

Comment puis-je le modifier pour qu'il ne renvoie aucun e-mail en double?

En d'autres termes, lorsque plusieurs lignes contiennent le même e-mail, je souhaite que les résultats n'incluent qu'une seule de ces lignes (de préférence la dernière). Les doublons dans d'autres colonnes doivent être autorisés.

Les clauses aiment DISTINCTet GROUP BYsemblent fonctionner sur des lignes entières. Je ne sais donc pas comment aborder cela.


2
Ok, vous devez utiliser PARTITION ou utiliser deux instructions select?
CarneyCode

Et que doit-on montrer s'il y a, par exemple, 2 lignes avec le même Email mais un ProductName différent? Le (de préférence le dernier) n'est pas clair. Dernier par quelle commande?
ypercubeᵀᴹ

@ypercube Comme indiqué dans la question, de préférence la dernière. Cependant, ce n'est pas vraiment critique pour moi. Je veux juste l'un d'eux.
Jonathan Wood

1
Vous pouvez regarder les questions suivantes: question1 , question2 ou question3 .
Marian

Pourquoi ne pouvez-vous pas utiliser: SELECT DISTINCT Email, ID, ProductName, ProductModel FROM Products?
Rick Henderson du

Réponses:


186

Si vous utilisez SQL Server 2005 ou supérieur, utilisez ceci:

SELECT *
  FROM (
                SELECT  ID, 
                        Email, 
                        ProductName, 
                        ProductModel,
                        ROW_NUMBER() OVER(PARTITION BY Email ORDER BY ID DESC) rn
                    FROM Products
              ) a
WHERE rn = 1

EDIT: Exemple utilisant une clause where:

SELECT *
  FROM (
                SELECT  ID, 
                        Email, 
                        ProductName, 
                        ProductModel,
                        ROW_NUMBER() OVER(PARTITION BY Email ORDER BY ID DESC) rn
                    FROM Products
                   WHERE ProductModel = 2
                     AND ProductName LIKE 'CYBER%'

              ) a
WHERE rn = 1

4
Je dois enquêter sur cette clause PARTITION, je ne l'ai jamais vue en action auparavant. Merci pour l'exemple
LorenVS

@Cybernate Une complication: mon intérieur a SELECTbesoin d'une WHEREcondition. Je pense que les numéros de ligne seront attribués à toutes les lignes du tableau. Cette syntaxe me dépasse un peu. Y a-t-il une chance d'une mise à jour qui garantirait une ligne avec un e-mail particulier remplissant la WHEREcondition?
Jonathan Wood

1
Vous pouvez ajouter une clause where au sql interne. Je mettrai à jour le message une fois que je pourrai accéder à mon ordinateur portable
Chandu

1
Mise à jour du message avec un exemple utilisant la clause where.
Chandu

1
Je fais cela fonctionne correctement uniquement lorsque je n'ai pas de JOIN s dans ma requête. Dès que j'ai un JOIN, le ROW_NUMBERretourne des valeurs beaucoup plus élevées que "1".
Uwe Keim le

10

Cela suppose que SQL Server 2005+ et que votre définition de «dernier» est le PK maximal pour un e-mail donné

WITH CTE AS
(
SELECT ID, 
       Email, 
       ProductName, 
       ProductModel, 
       ROW_NUMBER() OVER (PARTITION BY Email ORDER BY ID DESC) AS RowNumber 
FROM   Products
)
SELECT ID, 
       Email, 
       ProductName, 
       ProductModel
FROM CTE 
WHERE RowNumber = 1

6

Lorsque vous utilisez, DISTINCTpensez-y comme une ligne distincte et non comme une colonne. Il ne renverra que les lignes dont les colonnes ne correspondent pas exactement.

SELECT DISTINCT ID, Email, ProductName, ProductModel
FROM Products

----------------------
1 | something@something.com | ProductName1 | ProductModel1
2 | something@something.com | ProductName1 | ProductModel1

La requête renverrait les deux lignes car la IDcolonne est différente. Je suppose que la IDcolonne est une IDENTITYcolonne qui s'incrémente, si vous voulez retourner la dernière, je recommande quelque chose comme ceci:

SELECT DISTINCT TOP 1 ID, Email, ProductName, ProductModel
FROM Products
ORDER BY ID DESC

Le TOP 1renverra uniquement le premier enregistrement, en le triant par ordre IDdécroissant, il retournera les résultats avec la dernière ligne en premier. Cela vous donnera le dernier enregistrement.


2
Comme indiqué dans la question, je vois que DISTINCT fonctionne sur toute la ligne. Je veux faire comme vous le suggérez ci-dessus, mais pour chaque fois que l'e-mail est dupliqué dans les résultats (pas une seule fois).
Jonathan Wood

Dans ce cas, je recommanderais d'utiliser la réponse @Cybernate. Cela devrait faire exactement ce dont vous avez besoin.
jon3laze

4

Vous pouvez sur cela en utilisant la fonction GROUP BY

SELECT ID, Email, ProductName, ProductModel FROM Products GROUP BY Email


16
La colonne 'Products.ID' n'est pas valide dans la liste de sélection car elle n'est contenue ni dans une fonction d'agrégation ni dans la clause GROUP BY.
palota

2
Cela ne fonctionne pas sans utiliser quelque chose comme MAX (ID), MAX (ProductName), MAX (ProductModel) pour les autres colonnes
avl_sweden

2
Dans postgres, vous n'avez besoin que de la fonction d'agrégation sur la colonne qui sera utilisée dans la clause group by, par exemple SELECT id, max(email) AS email FROM tbl GROUP by email. Dans le serveur SQL, TOUTES les colonnes de la SELECTclause doivent être dans une fonction d'agrégation. Cela me mord à chaque fois que j'y retourne.
Bruce Pierson

Cela ne fonctionnera jamais. C'est une mauvaise solution
Dan AS

1

Pour Access, vous pouvez utiliser la requête SQL Select que je présente ici:

Par exemple, vous avez ce tableau:

CLIENTE || NOMBRES || COURRIER

888 || T800 ARNOLD || t800.arnold@cyberdyne.com

123 || JOHN CONNOR || s.connor@skynet.com

125 || SARAH CONNOR ||s.connor@skynet.com

Et vous devez sélectionner uniquement des e-mails distincts. Vous pouvez le faire avec ceci:

SÉLECTION SQL:

SELECT MAX(p.CLIENTE) AS ID_CLIENTE
, (SELECT TOP 1 x.NOMBRES 
    FROM Rep_Pre_Ene_MUESTRA AS x 
    WHERE x.MAIL=p.MAIL 
     AND x.CLIENTE=(SELECT MAX(l.CLIENTE) FROM Rep_Pre_Ene_MUESTRA AS l WHERE x.MAIL=l.MAIL)) AS NOMBRE, 
p.MAIL
FROM Rep_Pre_Ene_MUESTRA AS p
GROUP BY p.MAIL;

Vous pouvez l'utiliser pour sélectionner l'ID maximum, le nom correspondant à cet ID maximum, vous pouvez ajouter tout autre attribut de cette manière. Ensuite, à la fin, vous mettez la colonne distincte à filtrer et vous ne la regroupez qu'avec cette dernière colonne distincte.

Cela vous apportera l'ID maximum avec les données correspondantes, vous pouvez utiliser min ou toute autre fonction et vous répliquez cette fonction dans les sous-requêtes.

Cette sélection retournera:

CLIENTE || NOMBRES || COURRIER

888 || T800 ARNOLD || t800.arnold@cyberdyne.com

125 || SARAH CONNOR ||s.connor@skynet.com

N'oubliez pas d'indexer les colonnes que vous sélectionnez et la colonne distincte ne doit pas contenir de données numériques en majuscules ou en minuscules, sinon cela ne fonctionnera pas. Cela fonctionnera également avec un seul courrier recommandé. Bon codage !!!


0

La raison DISTINCTet le GROUP BYtravail sur des lignes entières est que votre requête renvoie des lignes entières.

Pour vous aider à comprendre: essayez d'écrire à la main ce que la requête doit renvoyer et vous verrez que ce qu'il faut mettre dans les colonnes non dupliquées est ambigu.

Si vous ne vous souciez littéralement pas de ce qui se trouve dans les autres colonnes, ne les renvoyez pas. Renvoyer une ligne aléatoire pour chaque adresse e-mail me semble un peu inutile.


@JohnFix Je veux renvoyer des lignes entières. Je ne veux tout simplement pas que les lignes soient renvoyées lorsque les résultats incluent déjà une ligne avec la même valeur dans la colonne Email.
Jonathan Wood

Alors, comment devrait-il décider lequel retourner? Voulez-vous vraiment une requête qui renvoie une ligne arbitraire pour chaque e-mail. Cela sent vraiment le besoin de repenser le problème que vous essayez de résoudre. Presque chaque fois qu'on me pose cette question (et cela revient souvent), il s'avère que le développeur n'a pas réfléchi aux conséquences de ce comportement dans l'application.
JohnFx

6
J'ai vraiment du mal à suivre votre logique. Comme indiqué dans la question, je préférerais le dernier (trié par ID). Oui, s'il sélectionnait une ligne aléatoire, ce serait bien. Et, oui, j'y ai pensé.
Jonathan Wood

0

Essaye ça

;With Tab AS (SELECT DISTINCT Email FROM  Products)
SELECT Email,ROW_NUMBER() OVER(ORDER BY Email ASC) AS  Id FROM Tab
ORDER BY Email ASC

-2

Essaye ça:

SELECT ID, Email, ProductName, ProductModel FROM Products WHERE ID IN (SELECT MAX(ID) FROM Products GROUP BY Email)

2
Pourquoi devrions-nous essayer cela? Pourquoi est-ce mieux que les autres réponses publiées ici au cours des 8 dernières années? Si vous souhaitez partager une meilleure façon de résoudre le problème, vous devez expliquer pourquoi vous le recommandez.
Dharman
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.