Найти дубликаты записей в 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;
Powerlord
источник
69
Будьте осторожны с подзапросами. Подзапросы являются / могут быть смехотворно плохими из-за проблем с производительностью. Если это должно происходить часто и / или с большим количеством повторяющихся записей, я бы рассмотрел перемещение обработки из базы данных в набор данных.
bdwakefield
11
Это некоррелированный подзапрос, поэтому он не должен быть слишком плохим, если только один запрос не плохо спроектирован.
ʞɔıu
Прекрасный. Думаю, это синтаксис «ОШИБКА 1248 (42000): у каждой производной таблицы должен быть свой псевдоним»
doublejosh
3
Это правильная идея, но опять же, как показано ниже, это работает, только если адреса гарантированно стандартизированы ...
Мэтт
30
+1 с помощью этого запроса вы можете найти дубликаты, а также тройные, четырехкратные ..... и так далее
albanx
352
SELECT date FROM logs group by date having count(*) >= 2
TRT
источник
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 необходим, если адрес может существовать более двух раз.

rudolfson
источник
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% работает и легко. Наслаждаться!!!

pratswinz
источник
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;
doublejosh
источник
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в нем.

Matt
источник
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). Четвертая строка гарантирует, что одна и та же запись не будет отображаться несколько раз в наборе результатов («дубликаты дубликатов»).

jerdiggity
источник
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

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

Quassnoi
источник
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;
Venkatesh
источник
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 ))
DJ.
источник
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'

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

Михал Малуга
источник
как это способствует чему-либо?
Кеннет Селеста