Tìm bản ghi trùng lặp trong MySQL


650

Tôi muốn lấy ra các bản ghi trùng lặp trong Cơ sở dữ liệu MySQL. Điều này có thể được thực hiện với:

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

Kết quả nào trong:

100 MAIN ST    2

Tôi muốn kéo nó để nó hiển thị mỗi hàng là một bản sao. Cái gì đó như:

JIM    JONES    100 MAIN ST
JOHN   SMITH    100 MAIN ST

Bất kỳ suy nghĩ về làm thế nào điều này có thể được thực hiện? Tôi đang cố gắng tránh thực hiện cái đầu tiên sau đó tìm kiếm các bản sao với truy vấn thứ hai trong mã.

Câu trả lời:


684

Điều quan trọng là viết lại truy vấn này để nó có thể được sử dụng như một truy vấn con.

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
Hãy cẩn thận với các truy vấn phụ. Các truy vấn phụ là / có thể rất tệ cho các mối quan tâm về hiệu suất. Nếu điều này cần xảy ra thường xuyên và / hoặc với nhiều bản ghi trùng lặp, tôi sẽ xem xét chuyển việc xử lý ra khỏi cơ sở dữ liệu và vào một tập dữ liệu.
bdwakefield

11
Đó là một truy vấn phụ không tương thích, vì vậy không nên quá tệ khi cho rằng một mình truy vấn không được thiết kế kém.
ʞɔIu

Đáng yêu. Đoán đây là sytax xung quanh "ERROR 1248 (42000): Mỗi bảng dẫn xuất phải có bí danh riêng"
doublejosh

3
Đây là ý tưởng đúng, nhưng một lần nữa, như dưới đây, điều này chỉ hoạt động nếu các địa chỉ được đảm bảo được chuẩn hóa ...
Matt

30
+1 với truy vấn này, bạn có thể tìm thấy các bản sao nhưng cũng có ba lần, gấp bốn lần ..... và cứ thế
albanx

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

5
Đây là truy vấn hoạt động dễ dàng nhất để sử dụng với Laravel. Chỉ cần thêm ->having(DB::raw('count(*)'), '>', 2)vào truy vấn. Cảm ơn nhiều!
Kahah 7/12/2015

1
Hoạt động tốt với bảng 10 triệu hàng. Đây phải là câu trả lời tốt nhất
Terry Lin

13
Hãy cẩn thận với câu trả lời này. Nó chỉ trả về một trong các bản sao. Nếu bạn có nhiều hơn 2 bản sao của cùng một bản ghi, bạn sẽ không thấy tất cả chúng và sau khi xóa bản ghi được trả về, bạn vẫn sẽ có các bản sao trong bảng của mình.
Mikiko Jane

7
Tại sao >=2? Chỉ cần sử dụngHAVING COUNT(*) > 1
BadHorsie

2
@TerryLin Xem xét rằng điều này không thực sự giải quyết được vấn đề đã nêu ban đầu (đó là cách trả lại tất cả các bản sao) Tôi không đồng ý.
Michael

198

Tại sao không chỉ tham gia vào bảng với chính nó?

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

Một DISTINCT là cần thiết nếu địa chỉ có thể tồn tại hơn hai lần.


20
Tôi cũng đã thử nghiệm điều này và nó chậm hơn gần 6 lần so với giải pháp được chấp nhận trong tình huống của tôi (MySQL mới nhất, bảng 120.000 hàng). Điều này có thể là do nó yêu cầu một bảng tạm thời, chạy EXPLAIN trên cả hai để thấy sự khác biệt.

4
Tôi đã thay đổi phần cuối của truy vấn để WHERE a.id > b.idchỉ lọc các bản sao mới hơn, theo cách đó tôi có thể thực hiện DELETEtrực tiếp kết quả. Chuyển so sánh để liệt kê các bản sao cũ hơn.
Stoffe

1
Mất 50 giây để chạy, câu trả lời của @ doublejosh mất 0,13 giây.
antonagestam

Tôi phải thêm rằng câu trả lời này đưa ra các câu trả lời trùng lặp mặc dù WHERE như trong trường hợp một địa chỉ bị tăng gấp ba, các hàng đầu ra được nhân đôi. Nếu nó tăng gấp bốn lần, tôi tin rằng phản hồi sẽ tăng gấp ba lần.
Wli

Tôi đã thử nghiệm điều này trong leetcode " leetcode.com/probols/d repeatate-emails ". Nó nhanh hơn so với truy vấn phụ.
cuồn cuộn

56

Tôi đã thử câu trả lời tốt nhất được chọn cho câu hỏi này, nhưng nó làm tôi bối rối phần nào. Tôi thực sự cần điều đó chỉ trên một lĩnh vực duy nhất từ ​​bảng của tôi. Ví dụ sau từ liên kết này hoạt động rất tốt đối với tôi:

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

Hoạt động như một lá bùa!
Vinícius

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

Đây là truy vấn tương tự mà bạn đã yêu cầu và 200% hoạt động cũng dễ dàng. Thưởng thức!!!


37

Điều này có dễ hơn không:

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

?


1
làm việc cho tôi khi tôi phải xử lý ~ 10 000 hàng trùng lặp để làm cho chúng trở nên độc đáo, nhanh hơn nhiều so với tải tất cả 600 000 hàng.
adrianTNT

1
dễ dàng hơn nhiều
Shwet

35

Tìm người dùng trùng lặp theo địa chỉ email với truy vấn này ...

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
Để tìm bản sao thực tế, bạn chỉ cần truy vấn bên trong. Đây là cách nhanh hơn so với các câu trả lời khác.
antonagestam

20

chúng ta có thể tìm thấy các bản sao phụ thuộc vào nhiều hơn một trường. Đối với những trường hợp bạn có thể sử dụng định dạng bên dưới.

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

16

Tìm địa chỉ trùng lặp phức tạp hơn nhiều so với vẻ ngoài của nó, đặc biệt nếu bạn yêu cầu độ chính xác. Một truy vấn MySQL là không đủ trong trường hợp này ...

Tôi làm việc tại SmartyStreets , nơi chúng tôi giải quyết việc xác thực và sao chép và các nội dung khác, và tôi đã thấy rất nhiều thách thức khác nhau với các vấn đề tương tự.

Có một số dịch vụ của bên thứ ba sẽ gắn cờ các bản sao trong danh sách cho bạn. Làm điều này chỉ với truy vấn con của MySQL sẽ không tính đến sự khác biệt về định dạng và tiêu chuẩn địa chỉ. USPS (đối với địa chỉ Hoa Kỳ) có một số hướng dẫn nhất định để thực hiện các tiêu chuẩn này, nhưng chỉ một số ít các nhà cung cấp được chứng nhận để thực hiện các hoạt động đó.

Vì vậy, tôi muốn giới thiệu câu trả lời tốt nhất cho bạn là xuất bảng thành tệp CSV và gửi nó đến bộ xử lý danh sách có khả năng. Một trong số đó là Công cụ xác thực địa chỉ hàng loạt SmartyStreets sẽ tự động hoàn thành cho bạn sau vài giây đến vài phút. Nó sẽ gắn cờ các hàng trùng lặp với một trường mới gọi là "Sao y" và giá trị của Ynó.


6
+1 để thấy khó khăn liên quan đến việc khớp chuỗi địa chỉ, mặc dù bạn có thể muốn chỉ định rằng câu hỏi "bản ghi trùng lặp" của OP không phức tạp, nhưng là khi so sánh địa chỉ
câu chuyện

13

Một giải pháp khác là sử dụng các bí danh bảng, như vậy:

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

Tất cả những gì bạn thực sự làm trong trường hợp này là lấy bảng danh sách ban đầu , tạo hai bảng p retend - p 1p 2 - từ đó, sau đó thực hiện nối trên cột địa chỉ (dòng 3). Dòng thứ 4 đảm bảo rằng cùng một bản ghi sẽ không hiển thị nhiều lần trong tập kết quả của bạn ("trùng lặp trùng lặp").


1
Hoạt động tốt. Nếu WHERE đang kiểm tra với THÍCH thì dấu nháy đơn cũng được tìm thấy. Làm cho truy vấn chậm hơn, nhưng trong trường hợp của tôi, nó là một bộ đếm thời gian.
gossi

10

Sẽ không hiệu quả lắm, nhưng nó sẽ hoạt động:

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

10

Điều này sẽ chọn trùng lặp trong một bảng, không có truy vấn con.

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

Truy vấn này thực sự mô phỏng ROW_NUMBER()hiện tại trong OracleSQL Server

Xem bài viết trong blog của tôi để biết chi tiết:


20
Không phải cho nitpick, nhưng FROM (SELECT ...) aoolà một truy vấn phụ :-P
Rocket Hazmat

8

Điều này cũng sẽ cho bạn thấy có bao nhiêu trùng lặp đã và sẽ sắp xếp kết quả mà không cần tham gia

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

hoàn hảo bởi vì nó vẫn cho biết có bao nhiêu mục được sao chép
chối

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

Đã thử cái này quá, nhưng dường như chỉ treo. Tin rằng trả về từ truy vấn bên trong không thỏa mãn định dạng tham số IN.
doublejosh

Ý bạn là gì không thỏa mãn định dạng tham số? Tất cả các nhu cầu IN là truy vấn con của bạn phải trả về một cột duy nhất. Nó thực sự khá đơn giản. Nhiều khả năng là truy vấn con của bạn đang được tạo trên một cột không được lập chỉ mục để nó mất một lượng thời gian không phù hợp để chạy. Tôi sẽ đề nghị nếu mất nhiều thời gian để chia nó thành hai truy vấn. Lấy truy vấn con, chạy nó trước tiên vào một bảng tạm thời, tạo một chỉ mục trên đó sau đó chạy truy vấn đầy đủ thực hiện truy vấn con trong đó trường trùng lặp của bạn trong bảng tạm thời.
Ryan Roper

Tôi đã lo lắng IN yêu cầu một danh sách được phân tách bằng dấu phẩy chứ không phải là một cột, điều này là sai. Đây là truy vấn phù hợp với tôi: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

Đối với bảng của bạn, nó sẽ là một cái gì đó như

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

Truy vấn này sẽ cung cấp cho bạn tất cả các mục nhập địa chỉ riêng biệt trong bảng danh sách của bạn ... Tôi không chắc cách này sẽ hoạt động như thế nào nếu bạn có bất kỳ giá trị khóa chính nào cho tên, v.v.


4

Thủ tục truy vấn loại bỏ trùng lặp nhanh nhất:

/* 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
Điều này rõ ràng chỉ xóa bản ghi đầu tiên từ mỗi nhóm trùng lặp.
Palec

4

Cá nhân truy vấn này đã giải quyết vấn đề của tôi:

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

Những gì tập lệnh này thực hiện là hiển thị tất cả các ID thuê bao tồn tại nhiều lần trong bảng và số lượng trùng lặp được tìm thấy.

Đây là các cột của bảng:

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

Hy vọng nó sẽ hữu ích cho bạn!


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

Thay thế thành phố bằng Bảng của bạn. Thay thế tên bằng tên trường của bạn


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

Để nhanh chóng xem các hàng trùng lặp, bạn có thể chạy một truy vấn đơn giản

Ở đây tôi đang truy vấn bảng và liệt kê tất cả các hàng trùng lặp với cùng user_id, market_place và 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;

Để xóa hàng trùng lặp, bạn phải quyết định hàng nào bạn muốn xóa. Ví dụ: người có id thấp hơn (thường là cũ hơn) hoặc có thể một số thông tin ngày khác. Trong trường hợp của tôi, tôi chỉ muốn xóa id thấp hơn vì id mới hơn là thông tin mới nhất.

Đầu tiên kiểm tra nếu các hồ sơ đúng sẽ bị xóa. Ở đây tôi đang chọn bản ghi trong số các bản sao sẽ bị xóa (theo id duy nhất).

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;

Sau đó, tôi chạy truy vấn xóa để xóa các bản sao:

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;

Sao lưu, kiểm tra hai lần, xác minh, xác minh sao lưu sau đó thực hiện.


-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

truy vấn phụ bên trong trả về các hàng có địa chỉ trùng lặp sau đó truy vấn phụ bên ngoài trả về cột địa chỉ cho địa chỉ có trùng lặp. truy vấn phụ bên ngoài chỉ phải trả về một cột vì nó được sử dụng làm toán hạng cho toán tử '= any'


-1

Chủ câu trả lời thực sự là tốt nhất và tôi muốn đề xuất thêm một thay đổi: sử dụng LIMIT để đảm bảo db sẽ không bị quá tải:

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

Đó là một thói quen tốt để sử dụng GIỚI HẠN nếu không có WHERE và khi thực hiện tham gia. Bắt đầu với giá trị nhỏ, kiểm tra mức độ nặng của truy vấn và sau đó tăng giới hạn.


Làm thế nào điều này đóng góp bất cứ điều gì cho bất cứ điều gì?
Kennet Celeste
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.