Rechercher des enregistrements en double dans MySQL


650

Je veux extraire des enregistrements en double dans une base de données MySQL. Cela peut être fait avec:

SELECT address, count(id) as cnt FROM list
GROUP BY address HAVING cnt > 1

Ce qui se traduit par:

100 MAIN ST    2

Je voudrais le retirer pour qu'il affiche chaque ligne en double. Quelque chose comme:

JIM    JONES    100 MAIN ST
JOHN   SMITH    100 MAIN ST

Avez-vous des réflexions sur la façon dont cela peut être fait? J'essaie d'éviter de faire le premier puis de rechercher les doublons avec une deuxième requête dans le code.

Réponses:


684

La clé est de réécrire cette requête afin qu'elle puisse être utilisée comme sous-requête.

SELECT firstname, 
   lastname, 
   list.address 
FROM list
   INNER JOIN (SELECT address
               FROM   list
               GROUP  BY address
               HAVING COUNT(id) > 1) dup
           ON list.address = dup.address;

69
Soyez prudent avec les sous-requêtes. Les sous-requêtes sont / peuvent être ridiculement mauvaises pour des problèmes de performances. Si cela doit se produire souvent et / ou avec de nombreux enregistrements en double, j'envisagerais de déplacer le traitement hors de la base de données et dans un ensemble de données.
bdwakefield

11
Il s'agit d'une sous-requête non corrélée, donc cela ne devrait pas être trop mauvais en supposant que l'une ou l'autre requête n'est pas mal conçue.
ʞɔıu

Charmant. Je suppose que c'est la syntaxe autour de "ERREUR 1248 (42000): Chaque table dérivée doit avoir son propre alias"
doublejosh

3
C'est la bonne idée, mais encore une fois, comme ci-dessous, cela ne fonctionne que si les adresses sont garanties d'être normalisées ...
Matt

30
+1 avec cette requête vous pouvez trouver des doublons mais aussi des triplicates, quadruplicates ..... et ainsi de suite
albanx

352
SELECT date FROM logs group by date having count(*) >= 2

5
C'était la requête de travail la plus simple à utiliser avec Laravel. Il fallait juste ajouter ->having(DB::raw('count(*)'), '>', 2)à la requête. Merci beaucoup!
Kovah

1
Fonctionne bien avec une table de 10 millions de lignes. Cela devrait être la meilleure réponse
Terry Lin

13
Soyez prudent avec cette réponse. Il ne renvoie qu'un seul des doublons. Si vous avez plus de 2 copies du même enregistrement, vous ne les verrez pas tous, et après avoir supprimé l'enregistrement renvoyé, vous aurez toujours des doublons dans votre tableau.
Mikiko Jane

7
Pourquoi >=2? Il suffit d'utiliserHAVING COUNT(*) > 1
BadHorsie

2
@TerryLin Étant donné que cela ne résout pas réellement le problème initialement indiqué (qui était de savoir comment renvoyer tous les doublons), je ne suis pas d'accord.
Michael

198

Pourquoi ne pas simplement INNER JOIN la table avec elle-même?

SELECT a.firstname, a.lastname, a.address
FROM list a
INNER JOIN list b ON a.address = b.address
WHERE a.id <> b.id

Un DISTINCT est nécessaire si l'adresse peut exister plus de deux fois.


20
Moi aussi, j'ai testé cela, et c'était presque 6 fois plus lent par rapport à la solution acceptée dans ma situation (dernier MySQL, table de 120 000 lignes). Cela peut être dû au fait qu'il nécessite une table temporaire, exécutez un EXPLAIN sur les deux pour voir les différences.

4
J'ai changé la dernière partie de la requête pour WHERE a.id > b.idfiltrer uniquement les doublons les plus récents, de cette façon je peux faire un DELETEdirectement sur le résultat. Basculez la comparaison pour répertorier les anciens doublons.
Stoffe

1
Cela a pris 50 secondes pour s'exécuter, la réponse de @ doublejosh a pris 0,13 seconde.
antonagestam

Je dois ajouter que cette réponse donne des réponses en double malgré le WHERE car dans le cas où une adresse est triplée, les lignes de sortie sont doublées. Si c'est quadruple, je pense que la réponse sera triplée.
Wli

J'ai testé cela dans leetcode " leetcode.com/problems/duplicate-emails ". C'était plus rapide que la sous-requête.
billow

56

J'ai essayé la meilleure réponse choisie pour cette question, mais cela m'a quelque peu dérouté. En fait, j'avais besoin de cela sur un seul champ de ma table. L'exemple suivant de ce lien a très bien fonctionné pour moi:

SELECT COUNT(*) c,title FROM `data` GROUP BY title HAVING c > 1;

Fonctionne comme un charme!
Vinícius

47
select `cityname` from `codcities` group by `cityname` having count(*)>=2

C'est la requête similaire que vous avez demandée et son fonctionnement à 200% et facile aussi. Prendre plaisir!!!


37

N'est-ce pas plus simple:

SELECT *
FROM tc_tariff_groups
GROUP BY group_id
HAVING COUNT(group_id) >1

?


1
travaillé pour moi où je devais juste traiter ~ 10 000 lignes en double afin de les rendre uniques, beaucoup plus rapide que de charger les 600 000 lignes.
adrianTNT

1
beaucoup plus facile
Shwet

35

Trouvez des utilisateurs en double par adresse e-mail avec cette requête ...

SELECT users.name, users.uid, users.mail, from_unixtime(created)
FROM users
INNER JOIN (
  SELECT mail
  FROM users
  GROUP BY mail
  HAVING count(mail) > 1
) dupes ON users.mail = dupes.mail
ORDER BY users.mail;

2
Pour trouver le doublon réel, vous n'avez besoin que de la requête interne. C'est bien plus rapide que les autres réponses.
antonagestam

20

nous pouvons trouver que les doublons dépendent également de plus d'un champ. Pour ces cas, vous pouvez utiliser le format ci-dessous.

SELECT COUNT(*), column1, column2 
FROM tablename
GROUP BY column1, column2
HAVING COUNT(*)>1;

16

La recherche d' adresses en double est beaucoup plus complexe qu'il n'y paraît, surtout si vous avez besoin de précision. Une requête MySQL ne suffit pas dans ce cas ...

Je travaille chez SmartyStreets , où nous traitons la validation et la déduplication et d'autres choses, et j'ai vu beaucoup de défis divers avec des problèmes similaires.

Il existe plusieurs services tiers qui signaleront les doublons dans une liste pour vous. Faire cela uniquement avec une sous-requête MySQL ne tiendra pas compte des différences de formats d'adresses et de normes. L'USPS (pour l'adresse aux États-Unis) a certaines directives pour rendre ces normes, mais seulement une poignée de fournisseurs sont certifiés pour effectuer de telles opérations.

Donc, je recommanderais la meilleure réponse pour vous est d'exporter la table dans un fichier CSV, par exemple, et de la soumettre à un processeur de liste capable. Un tel est SmartyStreets Bulk Address Validation Tool qui le fera pour vous en quelques secondes à quelques minutes automatiquement. Il signalera les lignes en double avec un nouveau champ appelé "Duplicate" et une valeur Ydedans.


6
+1 pour voir la difficulté de faire correspondre les chaînes d'adresses, bien que vous souhaitiez peut-être spécifier que la question des "enregistrements en double" du PO n'est pas complexe en soi, mais l'est lors de la comparaison des adresses
histoire

13

Une autre solution serait d'utiliser des alias de table, comme ceci:

SELECT p1.id, p2.id, p1.address
FROM list AS p1, list AS p2
WHERE p1.address = p2.address
AND p1.id != p2.id

Dans ce cas, tout ce que vous faites vraiment est de prendre la table de liste d' origine , de créer deux tables de rappel p - p 1 et p 2 - à partir de cela, puis d'effectuer une jointure sur la colonne d'adresse (ligne 3). La quatrième ligne garantit que le même enregistrement n'apparaît pas plusieurs fois dans votre ensemble de résultats ("doublons en double").


1
Fonctionne bien. Si le WHERE vérifie avec LIKE, des apostrophes sont également trouvées. Rend la requête plus lente, mais dans mon cas, il s'agit d'un retardateur.
gossi

10

Ça ne va pas être très efficace, mais ça devrait marcher:

SELECT *
FROM list AS outer
WHERE (SELECT COUNT(*)
        FROM list AS inner
        WHERE inner.address = outer.address) > 1;

10

Cela sélectionnera les doublons en une seule passe de table, pas de sous-requêtes.

SELECT  *
FROM    (
        SELECT  ao.*, (@r := @r + 1) AS rn
        FROM    (
                SELECT  @_address := 'N'
                ) vars,
                (
                SELECT  *
                FROM
                        list a
                ORDER BY
                        address, id
                ) ao
        WHERE   CASE WHEN @_address <> address THEN @r := 0 ELSE 0 END IS NOT NULL
                AND (@_address := address ) IS NOT NULL
        ) aoo
WHERE   rn > 1

Cette requête émule réellement ROW_NUMBER()présente dans OracleetSQL Server

Voir l'article dans mon blog pour plus de détails:


20
Pas pour faire une piqûre, mais FROM (SELECT ...) aooc'est une sous-requête :-P
Rocket Hazmat

8

Cela vous montrera également combien de doublons ont et ordonnera les résultats sans jointures

SELECT  `Language` , id, COUNT( id ) AS how_many
FROM  `languages` 
GROUP BY  `Language` 
HAVING how_many >=2
ORDER BY how_many DESC

parfait car il indique toujours combien d'entrées sont dupliquées
denis

4
 SELECT firstname, lastname, address FROM list
 WHERE 
 Address in 
 (SELECT address FROM list
 GROUP BY address
 HAVING count(*) > 1)

J'ai essayé celui-ci aussi, mais il semble juste se bloquer Croyez que le retour de la requête interne ne satisfait pas le format de paramètre IN.
doublejosh

Que voulez-vous dire ne satisfait pas le format de paramètre? Tous les besoins IN sont que votre sous-requête doit renvoyer une seule colonne. C'est vraiment assez simple. Il est plus probable que votre sous-requête soit générée sur une colonne qui n'est pas indexée, donc son exécution prend un temps excessif. Je dirais que si cela prend beaucoup de temps pour le diviser en deux requêtes. Prenez la sous-requête, exécutez-la d'abord dans une table temporaire, créez un index dessus, puis exécutez la requête complète en faisant la sous-requête où se trouve votre champ en double dans la table temporaire.
Ryan Roper

J'avais peur qu'IN exigeait une liste séparée par des virgules plutôt qu'une colonne, ce qui était tout simplement faux. Voici la requête qui a fonctionné pour moi:SELECT users.name, users.uid, users.mail, from_unixtime(created) FROM users INNER JOIN ( SELECT mail FROM users GROUP BY mail HAVING count(mail) > 1 ) dup ON users.mail = dup.mail ORDER BY users.mail, users.created;
doublejosh

4
select * from table_name t1 inner join (select distinct <attribute list> from table_name as temp)t2 where t1.attribute_name = t2.attribute_name

Pour votre table, ce serait quelque chose comme

select * from list l1 inner join (select distinct address from list as list2)l2 where l1.address=l2.address

Cette requête vous donnera toutes les entrées d'adresses distinctes dans votre table de liste ... Je ne sais pas comment cela fonctionnera si vous avez des valeurs de clé primaire pour le nom, etc.


4

Procédure de requête de suppression des doublons la plus rapide:

/* create temp table with one primary column id */
INSERT INTO temp(id) SELECT MIN(id) FROM list GROUP BY (isbn) HAVING COUNT(*)>1;
DELETE FROM list WHERE id IN (SELECT id FROM temp);
DELETE FROM temp;

2
De toute évidence, cela supprime uniquement le premier enregistrement de chaque groupe de doublons.
Palec

4

Personnellement, cette requête a résolu mon problème:

SELECT `SUB_ID`, COUNT(SRV_KW_ID) as subscriptions FROM `SUB_SUBSCR` group by SUB_ID, SRV_KW_ID HAVING subscriptions > 1;

Ce que ce script fait montre tous les ID d'abonné qui existent plus d'une fois dans la table et le nombre de doublons trouvés.

Ce sont les colonnes du tableau:

| SUB_SUBSCR_ID | int(11)     | NO   | PRI | NULL    | auto_increment |
| MSI_ALIAS     | varchar(64) | YES  | UNI | NULL    |                |
| SUB_ID        | int(11)     | NO   | MUL | NULL    |                |    
| SRV_KW_ID     | int(11)     | NO   | MUL | NULL    |                |

J'espère que cela vous sera utile non plus!


3
SELECT t.*,(select count(*) from city as tt where tt.name=t.name) as count FROM `city` as t where (select count(*) from city as tt where tt.name=t.name) > 1 order by count desc

Remplacez la ville par votre table. Remplacez le nom par le nom de votre champ


2
    SELECT *
    FROM (SELECT  address, COUNT(id) AS cnt
    FROM list
    GROUP BY address
    HAVING ( COUNT(id) > 1 ))

0
    Find duplicate Records:

    Suppose we have table : Student 
    student_id int
    student_name varchar
    Records:
    +------------+---------------------+
    | student_id | student_name        |
    +------------+---------------------+
    |        101 | usman               |
    |        101 | usman               |
    |        101 | usman               |
    |        102 | usmanyaqoob         |
    |        103 | muhammadusmanyaqoob |
    |        103 | muhammadusmanyaqoob |
    +------------+---------------------+

    Now we want to see duplicate records
    Use this query:


   select student_name,student_id ,count(*) c from student group by student_id,student_name having c>1;

+--------------------+------------+---+
| student_name        | student_id | c |
+---------------------+------------+---+
| usman               |        101 | 3 |
| muhammadusmanyaqoob |        103 | 2 |
+---------------------+------------+---+

0

Pour voir rapidement les lignes en double, vous pouvez exécuter une seule requête simple

Ici, je recherche le tableau et répertorie toutes les lignes en double avec les mêmes user_id, market_place et sku:

select user_id, market_place,sku, count(id)as totals from sku_analytics group by user_id, market_place,sku having count(id)>1;

Pour supprimer la ligne en double, vous devez décider quelle ligne vous souhaitez supprimer. Par exemple, celui avec un identifiant inférieur (généralement plus ancien) ou peut-être d'autres informations de date. Dans mon cas, je veux simplement supprimer l'ID inférieur car l'ID le plus récent est la dernière information.

Vérifiez d'abord si les bons enregistrements seront supprimés. Ici, je sélectionne l'enregistrement parmi les doublons qui seront supprimés (par identifiant unique).

select a.user_id, a.market_place,a.sku from sku_analytics a inner join sku_analytics b where a.id< b.id and a.user_id= b.user_id and a.market_place= b.market_place and a.sku = b.sku;

Ensuite, j'exécute la requête de suppression pour supprimer les dupes:

delete a from sku_analytics a inner join sku_analytics b where a.id< b.id and a.user_id= b.user_id and a.market_place= b.market_place and a.sku = b.sku;

Sauvegarde, double vérification, vérification, vérification de la sauvegarde puis exécution.


-1

select address from list where address = any (select address from (select address, count(id) cnt from list group by address having cnt > 1 ) as t1) order by address

la sous-requête interne renvoie des lignes avec une adresse en double, puis la sous-requête externe renvoie la colonne d'adresse pour l'adresse avec des doublons. la sous-requête externe doit renvoyer une seule colonne car elle a été utilisée comme opérande pour l'opérateur '= any'


-1

La réponse de Powerlord est en effet la meilleure et je recommanderais un autre changement: utilisez LIMIT pour vous assurer que db ne serait pas surchargé:

SELECT firstname, lastname, list.address FROM list
INNER JOIN (SELECT address FROM list
GROUP BY address HAVING count(id) > 1) dup ON list.address = dup.address
LIMIT 10

C'est une bonne habitude d'utiliser LIMIT s'il n'y a pas O WH et lors des jointures. Commencez avec une petite valeur, vérifiez le poids de la requête, puis augmentez la limite.


comment cela contribue-t-il à quelque chose?
Kennet Celeste
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.