Trova record duplicati in MySQL


650

Voglio estrarre record duplicati in un database MySQL. Questo può essere fatto con:

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

Che si traduce in:

100 MAIN ST    2

Vorrei tirarlo in modo che mostri ogni riga che è un duplicato. Qualcosa di simile a:

JIM    JONES    100 MAIN ST
JOHN   SMITH    100 MAIN ST

Qualche idea su come farlo? Sto cercando di evitare di fare il primo, quindi cercare i duplicati con una seconda query nel codice.

Risposte:


684

La chiave è riscrivere questa query in modo che possa essere utilizzata come sottoquery.

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
Fai attenzione alle domande secondarie. Le sottoquery sono / possono essere ridicolmente dannose per problemi di prestazioni. Se ciò dovesse accadere spesso e / o con molti record duplicati, prenderei in considerazione l'idea di spostare l'elaborazione fuori dal database e in un set di dati.
bdwakefield,

11
È una sottoquery non correlata, quindi non dovrebbe essere troppo male supponendo che entrambe le query da sole non siano mal progettate.
12ıu,

Bello. Suppongo che questa sia la sintassi di "ERRORE 1248 (42000): ogni tabella derivata deve avere il suo alias"
doublejosh,

3
Questa è l'idea giusta, ma di nuovo, come sotto, funziona solo se si garantisce che gli indirizzi siano standardizzati ...
Matt,

30
+1 con questa query puoi trovare duplicati ma anche triplicati, quadruplicati ..... e così via
albanx

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

5
Questa è stata la query di lavoro più semplice da utilizzare con Laravel. Ho dovuto solo aggiungere ->having(DB::raw('count(*)'), '>', 2)alla query. Grazie molto!
Kovah,

1
Funziona bene con una tabella da 10 milioni di righe. Questa dovrebbe essere la risposta migliore
Terry Lin

13
Stai attento con questa risposta. Restituisce solo uno dei duplicati. Se hai più di 2 copie dello stesso record, non le vedrai tutte e dopo aver eliminato il record restituito avrai ancora duplicati nella tabella.
Mikiko Jane,

7
Perché >=2? Basta usareHAVING COUNT(*) > 1
BadHorsie

2
@TerryLin Considerando che questo in realtà non risolve il problema originariamente dichiarato (che era come restituire tutti i duplicati) non sono d'accordo.
Michael,

198

Perché non solo INNER UNISCITI al tavolo con se stesso?

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

È necessario un DISTINCT se l'indirizzo potrebbe esistere più di due volte.


20
Anch'io ho provato questo ed era quasi 6 volte più lento rispetto alla soluzione accettata nella mia situazione (ultimo MySQL, tabella di 120.000 righe). Ciò potrebbe essere dovuto al fatto che richiede una tabella temporanea, eseguire un EXPLAIN su entrambi per vedere le differenze.

4
Ho modificato l'ultima parte della query per WHERE a.id > b.idfiltrare solo i duplicati più recenti, in questo modo posso fare un risultato DELETEdirettamente sul risultato. Cambia il confronto per elencare i duplicati più vecchi.
Stoffe

1
Ci vollero 50 secondi per eseguire, la risposta di @ doublejosh impiegò .13 secondi.
antonagestam,

Devo aggiungere che questa risposta fornisce risposte duplicate nonostante WHERE poiché nel caso in cui un indirizzo venga triplicato, le righe di output vengono raddoppiate. Se è quadrupla, credo che la risposta sarà triplicata.
Wli,

Ho provato questo nel leetcode " leetcode.com/problems/duplicate-emails ". È stato più veloce rispetto alla query secondaria.
Billow

56

Ho provato la migliore risposta scelta per questa domanda, ma mi ha confuso un po '. In realtà ne avevo bisogno solo su un singolo campo dal mio tavolo. Il seguente esempio di questo link ha funzionato molto bene per me:

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

Funziona come un fascino!
Vinícius,

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

Questa è la domanda simile che hai chiesto ed è funzionante al 200% e anche facile. Godere!!!


37

Non è più facile:

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

?


1
ha funzionato per me dove dovevo solo elaborare ~ 10.000 righe duplicate per renderle uniche, molto più veloci del caricamento di tutte le 600000 righe.
adrianTNT,

1
molto più facile
Shwet,

35

Trova utenti duplicati per indirizzo email con questa query ...

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
Per trovare il duplicato effettivo è necessaria solo la query interna. Questo è molto più veloce delle altre risposte.
antonagestam,

20

possiamo trovare che i duplicati dipendono anche da più di un campo. Per questi casi puoi usare il formato seguente.

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

16

Trovare indirizzi duplicati è molto più complesso di quanto sembri, specialmente se hai bisogno di precisione. Una query MySQL non è sufficiente in questo caso ...

Lavoro a SmartyStreets , dove ci occupiamo della convalida, della deduplicazione e di altre cose e ho visto molte sfide diverse con problemi simili.

Esistono diversi servizi di terze parti che contrassegneranno i duplicati in un elenco per te. Farlo esclusivamente con una subquery MySQL non terrà conto delle differenze nei formati e negli standard degli indirizzi. L'USPS (per gli indirizzi negli Stati Uniti) ha alcune linee guida per rendere questi standard, ma solo una manciata di venditori sono certificati per eseguire tali operazioni.

Pertanto, consiglierei la risposta migliore per te è esportare la tabella in un file CSV, ad esempio, e inviarlo a un processore di elenchi in grado. Uno di questi è SmartyStreets Bulk Address Validation Tool che lo farà automaticamente tra pochi secondi o pochi minuti. Contrassegnerà le righe duplicate con un nuovo campo chiamato "Duplicate" e un valore Yin esso.


6
+1 per vedere la difficoltà legata alla corrispondenza delle stringhe di indirizzi, anche se potresti voler specificare che la domanda "record duplicati" del PO non è di per sé complessa, ma è quando si confrontano gli indirizzi
storia

13

Un'altra soluzione sarebbe quella di utilizzare gli alias di tabella, in questo modo:

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

Tutto si sta facendo davvero in questo caso sta prendendo l'originale lista tavolo, creando due p tabelle retend - p 1 e p 2 - fuori da quella, e quindi eseguire un join sulla colonna di indirizzo (linea 3). La quarta riga si assicura che lo stesso record non compaia più volte nella tua serie di risultati ("duplicati duplicati").


1
Funziona bene. Se WHERE sta verificando con LIKE, vengono trovati anche gli apostrofi. Rende la query più lenta, ma nel mio caso è un timer.
gossi,

10

Non sarà molto efficiente, ma dovrebbe funzionare:

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

10

Questo selezionerà i duplicati in un passaggio di tabella, senza subquery.

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

Questa query emula attivamente i ROW_NUMBER()presenti in OracleeSQL Server

Vedi l'articolo nel mio blog per i dettagli:


20
Non da nitpick, ma FROM (SELECT ...) aooè una sottoquery :-P
Rocket Hazmat,

8

Questo ti mostrerà anche quanti duplicati hanno e ordinerà i risultati senza join

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

perfetto perché dice ancora quante voci sono duplicate
denis

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

Ho provato anche questo, ma sembra appendere. Credere che il ritorno dalla query interna non soddisfi il formato del parametro IN.
doublejosh,

Cosa intendi con non soddisfa il formato del parametro in? Tutto ciò che serve a IN è che la tua sottoquery debba restituire una singola colonna. È davvero piuttosto semplice. È più probabile che la tua sottoquery venga generata su una colonna non indicizzata, quindi l'esecuzione di una quantità eccessiva di tempo. Vorrei suggerire se ci vuole molto tempo per dividerlo in due query. Prendi la sottoquery, eseguila prima in una tabella temporanea, crea un indice su di essa, quindi esegui la query completa eseguendo la sottoquery in cui il tuo campo duplicato nella tabella temporanea.
Ryan Roper,

Ero preoccupato IN richiedeva un elenco separato da virgole piuttosto che una colonna, il che era semplicemente sbagliato. Ecco la domanda che ha funzionato per me: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

Per il tuo tavolo sarebbe qualcosa di simile

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

Questa query ti fornirà tutte le voci di indirizzo distinte nella tabella dell'elenco ... Non sono sicuro di come funzionerà se hai valori di chiave primaria per nome, ecc.


4

Procedura di query sulla rimozione dei duplicati più veloce:

/* 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
Questo ovviamente cancella solo il primo record da ciascun gruppo di duplicati.
Palec,

4

Personalmente questa query ha risolto il mio problema:

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

Quello che fa questo script è mostrare tutti gli ID abbonato che esistono più di una volta nella tabella e il numero di duplicati trovati.

Queste sono le colonne della tabella:

| 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    |                |

Spero che ti sia utile!


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

Sostituisci la città con il tuo tavolo. Sostituisci il nome con il nome del campo


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

Per visualizzare rapidamente le righe duplicate è possibile eseguire una singola query semplice

Qui sto interrogando la tabella e elencando tutte le righe duplicate con lo stesso user_id, market_place e 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;

Per eliminare la riga duplicata devi decidere quale riga vuoi eliminare. Ad esempio quello con ID inferiore (di solito più vecchio) o forse altre informazioni sulla data. Nel mio caso, desidero solo eliminare l'id inferiore poiché l'id più recente è costituito dalle informazioni più recenti.

Per prima cosa controlla se verranno eliminati i record corretti. Qui sto selezionando il record tra i duplicati che verranno eliminati (per ID univoco).

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;

Quindi eseguo la query di eliminazione per eliminare i duplicati:

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;

Backup, ricontrolla, verifica, verifica backup ed esegui.


-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 query secondaria interna restituisce righe con indirizzo duplicato, quindi la query secondaria esterna restituisce la colonna dell'indirizzo per l'indirizzo con duplicati. la query secondaria esterna deve restituire solo una colonna perché utilizzata come operando per l'operatore '= any'


-1

La risposta di Powerlord è davvero la migliore e consiglierei un'altra modifica: usare LIMIT per assicurarsi che db non venga sovraccaricato:

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

È una buona abitudine usare LIMIT se non c'è DOVE e quando si effettuano i join. Inizia con un valore piccolo, controlla quanto è pesante la query e quindi aumenta il limite.


in che modo questo contribuisce a qualcosa?
Kennet Celeste,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.