Znajdź zduplikowane rekordy w MySQL

650

Chcę wyciągnąć zduplikowane rekordy z bazy danych MySQL. Można to zrobić za pomocą:

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

Co skutkuje w:

100 MAIN ST    2

Chciałbym go wyciągnąć, aby pokazywał każdy wiersz, który jest duplikatem. Coś jak:

JIM    JONES    100 MAIN ST
JOHN   SMITH    100 MAIN ST

Wszelkie przemyślenia na temat tego, jak można to zrobić? Próbuję uniknąć wykonania pierwszego, a następnie wyszukania duplikatów za pomocą drugiego zapytania w kodzie.

Chris Bartow
źródło

Odpowiedzi:

684

Kluczem jest przepisanie tego zapytania, aby można było go użyć jako podzapytania.

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;
Władca
źródło
69
Zachowaj ostrożność przy zadawaniu pytań Pod-zapytania są / mogą być absurdalnie złe z powodu problemów z wydajnością. Jeśli to musi się zdarzać często i / lub z dużą ilością zduplikowanych rekordów, rozważę przeniesienie przetwarzania z bazy danych do zestawu danych.
bdwakefield
11
Jest to nieskorelowane podzapytanie, więc nie powinno być tak źle, zakładając, że jedno zapytanie nie jest źle zaprojektowane.
Mayıu
Śliczny. Zgadnij, to jest sytax wokół „BŁĄD 1248 (42000): Każda tabela pochodna musi mieć swój własny alias”
doublejosh
3
To dobry pomysł, ale znowu, jak poniżej, działa to tylko wtedy, gdy adresy mają być znormalizowane ...
Matt
30
+1 za pomocą tego zapytania można znaleźć duplikaty, ale także trzykrotnie, czterokrotnie ..... i tak dalej
albanx
352
SELECT date FROM logs group by date having count(*) >= 2
trt
źródło
5
To było najłatwiej działające zapytanie do użycia z Laravelem. Po prostu musiałem dodać ->having(DB::raw('count(*)'), '>', 2)do zapytania. Wielkie dzięki!
Kovah,
1
Działa dobrze z tabelą 10 milionów wierszy. To powinna być najlepsza odpowiedź
Terry Lin
13
Uważaj z tą odpowiedzią. Zwraca tylko jeden z duplikatów. Jeśli masz więcej niż 2 kopie tego samego rekordu, nie zobaczysz ich wszystkich, a po usunięciu rekordu wrócisz, nadal będziesz mieć duplikaty w swojej tabeli.
Mikiko Jane
7
Dlaczego >=2? Wystarczy użyćHAVING COUNT(*) > 1
BadHorsie
2
@TerryLin Biorąc pod uwagę, że to tak naprawdę nie rozwiązuje pierwotnie stwierdzonego problemu (czyli sposobu zwrotu wszystkich duplikatów), nie zgadzam się.
Michael
198

Dlaczego po prostu INNER DOŁĄCZ DO stołu?

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

DISTINCT jest potrzebny, jeśli adres może istnieć więcej niż dwa razy.

Rudolfson
źródło
20
Ja też to przetestowałem i było prawie 6 razy wolniejsze w porównaniu do przyjętego rozwiązania w mojej sytuacji (najnowszy MySQL, tabela 120 000 wierszy). Może to być spowodowane tym, że wymaga tymczasowej tabeli, uruchom EXPLAIN na obu, aby zobaczyć różnice.
4
Zmieniłem ostatnią część zapytania, aby WHERE a.id > b.idodfiltrować tylko nowsze duplikaty, w ten sposób mogę zrobić DELETEbezpośrednio na wyniku. Przełącz porównanie, aby wyświetlić listę starszych duplikatów.
Stoffe,
1
Uruchomienie zajęło 50 sekund, odpowiedź @ doublejosh zajęła 0,13 sekundy.
antonagestam
Muszę dodać, że odpowiedź ta daje zduplikowane odpowiedzi pomimo GDZIE, ponieważ w przypadku potrojenia jednego adresu wiersze wyjściowe są podwojone. Jeśli będzie czterokrotnie, wierzę, że odpowiedź potroi się.
Wli
Przetestowałem to w leetcode „ leetcode.com/problems/duplicate-emails ”. Było to szybsze w porównaniu do zapytania podrzędnego.
kłębek
56

Próbowałem wybrać najlepszą odpowiedź na to pytanie, ale nieco mnie to pomieszało. Tak naprawdę potrzebowałem tego tylko na jednym polu z mojego stołu. Poniższy przykład z tego linku działał dla mnie bardzo dobrze:

SELECT COUNT(*) c,title FROM `data` GROUP BY title HAVING c > 1;
Arman Malik
źródło
Działa jak marzenie!
Vinícius
47
select `cityname` from `codcities` group by `cityname` having count(*)>=2

Jest to podobne zapytanie, o które prosiłeś, a jego 200% działa i jest łatwe. Cieszyć się!!!

pratswinz
źródło
37

Czy to nie jest łatwiejsze:

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

?

Tudor
źródło
1
pracował dla mnie, gdzie musiałem przetworzyć ~ 10 000 zduplikowanych wierszy, aby uczynić je unikalnymi, znacznie szybszymi niż ładowanie wszystkich 600 000 wierszy.
adrianTNT
1
o wiele łatwiej
Shwet
35

Znajdź zduplikowanych użytkowników według adresu e-mail za pomocą tego zapytania ...

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;
doublejosh
źródło
2
Aby znaleźć rzeczywisty duplikat, potrzebujesz tylko wewnętrznego zapytania. Jest to o wiele szybsze niż inne odpowiedzi.
antonagestam
20

możemy znaleźć duplikaty również zależy od więcej niż jednego pola. W tych przypadkach można użyć formatu poniżej.

SELECT COUNT(*), column1, column2 
FROM tablename
GROUP BY column1, column2
HAVING COUNT(*)>1;
KESAVAN PURUSOTHAMAN
źródło
16

Znalezienie zduplikowanych adresów jest znacznie bardziej skomplikowane, niż się wydaje, szczególnie jeśli potrzebujesz dokładności. W tym przypadku zapytanie MySQL nie wystarczy ...

Pracuję w SmartyStreets , gdzie zajmujemy się weryfikacją i usuwaniem duplikatów i innymi rzeczami, i widziałem wiele różnorodnych wyzwań z podobnymi problemami.

Istnieje kilka usług stron trzecich, które oznaczą dla Ciebie duplikaty na liście. Wykonanie tego wyłącznie z podzapytaniem MySQL nie uwzględni różnic w formatach i standardach adresów. USPS (dla adresu w USA) ma pewne wytyczne, aby uczynić te standardy, ale tylko garstka dostawców jest certyfikowana do wykonywania takich operacji.

Tak więc polecam najlepszą odpowiedzią dla ciebie jest na przykład wyeksportowanie tabeli do pliku CSV i przesłanie jej do odpowiedniego procesora list. Jednym z nich jest SmartyStreets Bulk Address Validation Tool, które automatycznie wykona to za kilka sekund do kilku minut. Będzie to oznaczać duplikaty wierszy z nowym polem o nazwie „Duplikuj” i wartością Yw nim.

Matt
źródło
6
+1 za dostrzeżenie trudności związanych z dopasowywaniem ciągów adresów, chociaż możesz chcieć sprecyzować, że pytanie „duplikaty rekordów” PO nie jest samo w sobie skomplikowane, ale ma miejsce przy porównywaniu adresów
historia
13

Innym rozwiązaniem byłoby użycie aliasów tabeli, takich jak:

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

Wszystko tak naprawdę robi w tym przypadku bierze oryginalnej listy stół, tworząc dwa p tabelach retend - p 1 i p 2 , a następnie wykonanie łączenia w kolumnie adresu (wiersz 3). Czwarty wiersz zapewnia, że ​​ten sam rekord nie pojawi się wiele razy w zestawie wyników („duplikaty duplikatów”).

jerdiggity
źródło
1
Działa dobrze. Jeśli GDZIE sprawdza się PODOBNIE, wówczas znajdują się również apostrofy. Powoduje spowolnienie zapytania, ale w moim przypadku jest to jednorazowy czas.
gossi
10

Nie będzie bardzo wydajny, ale powinien działać:

SELECT *
FROM list AS outer
WHERE (SELECT COUNT(*)
        FROM list AS inner
        WHERE inner.address = outer.address) > 1;
Chad Birch
źródło
10

Spowoduje to wybranie duplikatów w jednym przebiegu tabeli, bez podkwerend.

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

To zapytanie aktywnie emuluje ROW_NUMBER()obecne w OracleiSQL Server

Zobacz artykuł na moim blogu, aby uzyskać szczegółowe informacje:

Quassnoi
źródło
20
Nie do odegrania, ale FROM (SELECT ...) aoojest podkwerendą :-P
Rocket Hazmat
8

To również pokaże, ile kopii ma i uporządkuje wyniki bez łączenia

SELECT  `Language` , id, COUNT( id ) AS how_many
FROM  `languages` 
GROUP BY  `Language` 
HAVING how_many >=2
ORDER BY how_many DESC
Martin Tonev
źródło
idealne, ponieważ wciąż mówi, ile wpisów jest zduplikowanych
denis
4
 SELECT firstname, lastname, address FROM list
 WHERE 
 Address in 
 (SELECT address FROM list
 GROUP BY address
 HAVING count(*) > 1)
Ryan Roper
źródło
Próbowałem też, ale wydaje się, że po prostu się zawiesił. Uwierz, że zwrot z wewnętrznego zapytania nie spełnia formatu parametru IN.
doublejosh
Co masz na myśli mówiąc, że nie spełnia formatu parametrów? Wszystko, czego potrzeba, to to, że twoje podzapytanie musi zwrócić jedną kolumnę. To naprawdę bardzo proste. Bardziej prawdopodobne jest, że twoje podzapytanie jest generowane w kolumnie, która nie jest indeksowana, więc uruchomienie zajmuje zbyt dużo czasu. Sugerowałbym, jeśli zajmuje dużo czasu, aby rozbić go na dwa zapytania. Weź podzapytanie, uruchom je najpierw do tabeli tymczasowej, utwórz na nim indeks, a następnie uruchom pełne zapytanie, wykonując podzapytanie w miejscu, w którym znajduje się duplikat pola w tabeli tymczasowej.
Ryan Roper
Martwiłem się, że IN wymaga listy oddzielonej przecinkami, a nie kolumny, co było po prostu błędne. Oto zapytanie, które zadziałało dla mnie: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

Dla twojego stołu byłoby to coś w rodzaju

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

To zapytanie da ci wszystkie odrębne wpisy adresu w tabeli list ... Nie jestem pewien, jak to będzie działać, jeśli masz jakieś wartości klucza podstawowego dla nazwy itp.

Neha Patil
źródło
4

Procedura najszybszych zapytań dotyczących usuwania duplikatów:

/* 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;
venkatesh
źródło
2
To oczywiście usuwa tylko pierwszy rekord z każdej grupy duplikatów.
Palec
4

Osobiście to zapytanie rozwiązało mój problem:

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

Skrypt ten pokazuje wszystkie identyfikatory subskrybentów, które istnieją więcej niż jeden raz, w tabeli i liczbę znalezionych duplikatów.

Oto kolumny tabeli:

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

Mam nadzieję, że będzie to pomocne dla Ciebie!

Ionut Petre
źródło
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

Zastąp miasto swoim stołem. Zastąp nazwę swoją nazwą pola

Lalit Patel
źródło
2
    SELECT *
    FROM (SELECT  address, COUNT(id) AS cnt
    FROM list
    GROUP BY address
    HAVING ( COUNT(id) > 1 ))
DJ.
źródło
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 |
+---------------------+------------+---+
Usman Yaqoob
źródło
0

Aby szybko zobaczyć zduplikowane wiersze, możesz uruchomić jedno proste zapytanie

Tutaj sprawdzam tabelę i wymieniam wszystkie zduplikowane wiersze z tym samym identyfikatorem użytkownika, rynkiem i 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;

Aby usunąć zduplikowany wiersz, musisz zdecydować, który wiersz chcesz usunąć. Np. Ten o niższym identyfikatorze (zwykle starszy) lub może jakiś inny termin. W moim przypadku chcę tylko usunąć dolny identyfikator, ponieważ nowszy identyfikator to najnowsze informacje.

Najpierw sprawdź dwukrotnie, czy odpowiednie rekordy zostaną usunięte. Tutaj wybieram rekord spośród duplikatów, które zostaną usunięte (według unikalnego identyfikatora).

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;

Następnie uruchamiam zapytanie o usunięcie, aby usunąć duplikaty:

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;

Utwórz kopię zapasową, sprawdź dwukrotnie, sprawdź, sprawdź kopię zapasową, a następnie uruchom.

Ganesh Krishnan
źródło
-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

wewnętrzne zapytanie podrzędne zwraca wiersze ze zduplikowanym adresem, a następnie zewnętrzne zapytanie podrzędne zwraca kolumnę adresu dla adresu z duplikatami. zewnętrzne zapytanie cząstkowe musi zwrócić tylko jedną kolumnę, ponieważ zostało użyte jako operand dla operatora „= any”

aad
źródło
-1

Odpowiedź Powerlorda jest rzeczywiście najlepsza i zaleciłbym jeszcze jedną zmianę: użyj LIMIT, aby upewnić się, że db nie zostanie przeciążony:

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

Dobrym nawykiem jest używanie LIMITU, jeśli nie ma GDZIE i podczas łączenia. Zacznij od małej wartości, sprawdź, jak duże jest zapytanie, a następnie zwiększ limit.

Michał Maluga
źródło
jak to się do czegoś przyczynia?
Kennet Celeste