Найти дубликаты записей в MySQL


650

Я хочу вытащить дубликаты записей в базе данных MySQL. Это можно сделать с помощью:

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

Что приводит к:

100 MAIN ST    2

Я хотел бы вытащить его так, чтобы он показывал каждую строку, которая является дубликатом. Что-то вроде:

JIM    JONES    100 MAIN ST
JOHN   SMITH    100 MAIN ST

Есть мысли о том, как это можно сделать? Я стараюсь не делать первый, а затем искать дубликаты со вторым запросом в коде.

Ответы:


684

Ключ заключается в том, чтобы переписать этот запрос, чтобы он мог использоваться как подзапрос.

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
Будьте осторожны с подзапросами. Подзапросы являются / могут быть смехотворно плохими из-за проблем с производительностью. Если это должно происходить часто и / или с большим количеством повторяющихся записей, я бы рассмотрел перемещение обработки из базы данных в набор данных.
— bdwakefield

11
Это некоррелированный подзапрос, поэтому он не должен быть слишком плохим, если только один запрос не плохо спроектирован.
— ʞɔıu

Прекрасный. Думаю, это синтаксис «ОШИБКА 1248 (42000): у каждой производной таблицы должен быть свой псевдоним»
— doublejosh

3
Это правильная идея, но опять же, как показано ниже, это работает, только если адреса гарантированно стандартизированы ...
— Мэтт

30
+1 с помощью этого запроса вы можете найти дубликаты, а также тройные, четырехкратные ..... и так далее
— albanx

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

5
Это был самый простой рабочий запрос для использования с Laravel. Просто нужно было добавить ->having(DB::raw('count(*)'), '>', 2)к запросу. Большое спасибо!
— Кова

1
Хорошо работает с таблицей 10 миллионов строк. Это должен быть лучший ответ
— Терри Лин

13
Будьте осторожны с этим ответом. Возвращает только один из дубликатов. Если у вас есть более 2 копий одной и той же записи, вы не увидите их все, и после удаления возвращенной записи в вашей таблице все равно останутся дубликаты.
— Микико Джейн

7
Почему >=2? Просто используйтеHAVING COUNT(*) > 1
— BadHorsie

2
@TerryLin Учитывая, что это на самом деле не решает первоначально заявленную проблему (которая заключалась в том, как вернуть все дубликаты), я не согласен.
— Майкл

198

Почему бы просто не присоединиться к столу с самим собой?

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 необходим, если адрес может существовать более двух раз.


20
Я тоже это проверял, и это было почти в 6 раз медленнее по сравнению с принятым в моей ситуации решением (последняя версия MySQL, таблица из 120 000 строк). Это может быть связано с тем, что требуется временная таблица, запустите EXPLAIN для обоих, чтобы увидеть различия.

4
Я изменил последнюю часть запроса, чтобы WHERE a.id > b.idотфильтровывать только более новые дубликаты, чтобы я мог DELETEнапрямую влиять на результат. Переключите сравнение, чтобы получить список старых дубликатов.
— Стоффе

1
Это заняло 50 секунд, ответ @ doublejosh занял .13 секунд.
— антагонестам

Я должен добавить, что этот ответ дает повторяющиеся ответы, несмотря на то, ГДЕ, поскольку в случае утроения одного адреса, выходные строки удваиваются. Если это будет в четыре раза, я думаю, что ответ будет утроен.
— Вт

Я проверил это в leetcode " leetcode.com/problems/duplicate-emails ". Это было быстрее по сравнению с подзапросом.
— вал

56

Я попробовал лучший ответ, выбранный для этого вопроса, но это несколько смутило меня. Я действительно нуждался в этом только на одном поле из моей таблицы. Следующий пример по этой ссылке сработал очень хорошо для меня:

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

Работает как шарм!
— Виниций

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

Это тот же запрос, который вы просили, и он на 200% работает и легко. Наслаждаться!!!


37

Разве это не проще?

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

?


1
работал для меня, где мне нужно было просто обработать ~ 10 000 дублирующихся строк, чтобы сделать их уникальными, намного быстрее, чем загрузить все 600 000 строк.
— adrianTNT

1
намного проще
— Shwet

35

Найти повторяющихся пользователей по адресу электронной почты с этим запросом ...

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
Чтобы найти дубликат, вам нужен только внутренний запрос. Это намного быстрее, чем другие ответы.
— антагонестам

20

мы можем найти дубликаты, зависит также от более чем одного поля. Для этих случаев вы можете использовать ниже формат.

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

16

Поиск повторяющихся адресов гораздо сложнее, чем кажется, особенно если вам требуется точность. В этом случае недостаточно запроса MySQL ...

Я работаю на SmartyStreets , где мы занимаемся валидацией, дедупликацией и другими вещами, и я видел много разных проблем с похожими проблемами.

Существует несколько сторонних сервисов, которые помечают дубликаты в списке для вас. Выполнение этого только с подзапросом MySQL не будет учитывать различия в форматах и ​​стандартах адресов. USPS (для адреса в США) имеет определенные руководящие принципы, чтобы сделать эти стандарты, но только несколько поставщиков сертифицированы для выполнения таких операций.

Поэтому я бы порекомендовал вам лучший ответ - например, экспортировать таблицу в CSV-файл и отправить ее в работающий обработчик списков. Одним из таких инструментов является SmartyStreets Bulk Address Valid Tool, который сделает это за вас от нескольких секунд до нескольких минут автоматически. Он будет отмечать повторяющиеся строки с новым полем с именем «Duplicate» и значением Yв нем.


6
+1 за то, что вы видите трудности, связанные с сопоставлением адресных строк, хотя вы можете указать, что вопрос OP о «повторяющихся записях» сам по себе не сложен, а заключается в сравнении адресов
— история

13

Другое решение - использовать псевдонимы таблиц, например:

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

Все, что вы действительно делаете в этом случае, - это извлечение исходной таблицы списков , создание из нее двух таблиц p- retend - p 1 и p 2 , а затем выполнение соединения со столбцом адреса (строка 3). Четвертая строка гарантирует, что одна и та же запись не будет отображаться несколько раз в наборе результатов («дубликаты дубликатов»).


1
Работает хорошо. Если ГДЕ проверяется как LIKE, то также обнаруживаются апострофы. Делает запрос медленнее, но в моем случае это таймер.
— Госси

10

Не будет очень эффективным, но оно должно работать:

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

10

Это позволит выбрать дубликаты за один проход таблицы, без подзапросов.

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

Этот запрос фактически эмулирует ROW_NUMBER()присутствующие OracleиSQL Server

Смотрите статью в моем блоге для деталей:


20
Не для придирки, но FROM (SELECT ...) aooэто подзапрос :-P
— Rocket Hazmat

8

Это также покажет вам, сколько дубликатов есть, и упорядочит результаты без объединений.

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

идеально, потому что он все еще говорит, сколько записей дублируется
— Денис

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

Пробовал и этот тоже, но вроде просто завис. Поверьте, что возврат из внутреннего запроса не удовлетворяет формату параметра IN.
— DoubleJosh

Что вы имеете в виду не удовлетворяет в формате параметра? Все, что нужно для IN - это чтобы ваш подзапрос возвращал один столбец. Это действительно довольно просто. Скорее всего, ваш подзапрос генерируется в столбце, который не проиндексирован, поэтому для его выполнения требуется слишком много времени. Я хотел бы предложить, если это займет много времени, чтобы разбить его на два запроса. Возьмите подзапрос, сначала запустите его во временную таблицу, создайте для него индекс, а затем выполните полный запрос, выполнив подзапрос, где ваше дублирующее поле во временной таблице.
— Райан Ропер

Я был обеспокоен тем, что IN требуется список, разделенный запятыми, а не столбец, что было неправильно. Вот запрос, который работал для меня: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

Для вашего стола это было бы что-то вроде

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

Этот запрос даст вам все отдельные записи адресов в таблице списка ... Я не уверен, как это будет работать, если у вас есть какие-либо значения первичного ключа для имени и т. Д.


4

Самая быстрая процедура удаления дубликатов:

/* 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
Это, очевидно, удаляет только первую запись из каждой группы дубликатов.
— Палек

4

Лично этот запрос решил мою проблему:

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

Этот скрипт показывает все идентификаторы абонента, которые существуют более одного раза, в таблице и количество найденных дубликатов.

Это столбцы таблицы:

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

Надеюсь, это будет полезно и для вас!


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

Замените город своей таблицей. Замените имя на имя вашего поля


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

Чтобы быстро увидеть повторяющиеся строки, вы можете выполнить один простой запрос

Здесь я опрашиваю таблицу и перечисляю все повторяющиеся строки с одинаковыми user_id, market_place и 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;

Чтобы удалить дублирующую строку, вы должны решить, какую строку вы хотите удалить. Например, с более низким идентификатором (обычно старше) или, возможно, с другой информацией о дате. В моем случае я просто хочу удалить нижний идентификатор, поскольку более новый идентификатор является самой последней информацией.

Сначала проверьте, удаляются ли нужные записи. Здесь я выбираю запись среди дубликатов, которые будут удалены (по уникальному идентификатору).

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;

Затем я запускаю запрос на удаление, чтобы удалить обманщиков:

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;

Резервное копирование, Двойная проверка, проверка, проверка резервной копии, затем выполнить.


-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

внутренний подзапрос возвращает строки с дублирующимся адресом, затем внешний подзапрос возвращает адресный столбец для адреса с дубликатами. внешний подзапрос должен возвращать только один столбец, поскольку он используется в качестве операнда для оператора '= any'


-1

Ответ Powerlord действительно лучший, и я бы рекомендовал еще одно изменение: используйте LIMIT, чтобы убедиться, что db не будет перегружен:

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

Хорошей привычкой является использование LIMIT, если нет WHERE и при создании соединений. Начните с малого значения, проверьте, насколько тяжелым является запрос, а затем увеличьте лимит.


как это способствует чему-либо?
— Кеннет Селеста
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.