← Все курсы · Курс SQL · Все руководства

Как найти повторяющиеся строки в SQL (и решить, что считать одной)

Сначала определите дубликат, затем GROUP BY найдет каждую копию · Написано для прослушивания · Часть Analyst Prep Kit

К , аналитик данных · Опубликовано

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

Вот что на самом деле с этим делать. В следующей таблице, которую вы получите, запустите одно сравнение прежде всего: COUNT(*) против COUNT(DISTINCT key). Если эти два числа различаются, в таблице есть дубликаты, и теперь вы узнаете об этом еще до того, как это произойдет в первом отчете.

Краткая версия: группируйте по столбцам, которые определяют дубликат, сохраняйте группы там, где COUNT(*) больше единицы, и отметьте дополнительные функции значком ROW_NUMBER вместо того, чтобы удалять их.

Одна картинка несет в себе весь метод. Строки с общим ключом сворачиваются в сегменты, а сегменты, содержащие более одной строки, являются вашими дубликатами.

Grouping exposes the duplicates A left-to-right picture in three stages. Stage one is a column of eight row boxes. Three of them carry the same small square marker, and two others share a different marker, showing that they hold the same key value. Stage two shows the rows collapsed into five buckets: one bucket holds the three matching rows, one holds the two matching rows, and three buckets hold a single row each. The two buckets holding more than one row are outlined in a warm warning color and labelled with their counts, three and two. Stage three shows only those two flagged buckets passing through to the result. The picture shows that grouping rows by their key makes every duplicate visible as a bucket whose count is above one, while unique rows form buckets of one and drop away. rows groups count > 1 3 2 1 1 3 copies 2 copies
Сгруппируйте строки по столбцам, которые определяют дубликат. Ведра одного уникальны и отпадают. Ведра выше единицы — это ваши дубликаты, а счетчик показывает, сколько копий.
Что вы узнаете
  1. Решите, что считать дубликатом, прежде чем писать что-либо.
  2. Десятисекундный тест на любом столе
  3. Перечислите дубликаты с помощью GROUP BY и HAVING.
  4. Просматривайте полные повторяющиеся строки, а не только сводку.
  5. Отметьте хранителей и статистов с помощью ROW_NUMBER
  6. Шаблон удаления, указанный один раз, с ограждениями
  7. Полное до и после
  8. Крайние случаи, из-за которых поиск дубликатов идет не так
  9. Почему это работает
  10. Использование этого в вашем собственном проекте
  11. Все на одном экране
Проработанный пример реальный. Каждое число на этой странице взято из 14-строчной таблицы клиентов, которую я построил с тремя дубликатами, специально засеянными, и каждый запрос выполнялся к ней в SQLite, прежде чем его выходные данные были вставлены сюда. Таблица достаточно маленькая, чтобы ее можно было проверить на глаз, и в этом суть: каждый результат вы можете подтвердить самостоятельно. Если GROUP BY сам новый, читайте GROUP BY и HAVING сначала и возвращайся.

Вот таблица. Четырнадцать строк, и система регистрации дала сбой тремя разными способами.

customer_idэлектронная почтаполное имяГородДата_регистрации
101ana@keller.comАна КеллерОстин2026-01-04
102ben.ortiz@mail.comБен ОртисДаллас2026-01-09
103cara.li@mail.comДорогая ЛиОстин2026-01-15
104dev.p@mail.comDev PatelХьюстон2026-01-20
105ella.r@mail.comЭлла РейесДаллас2026-02-02
106finn.w@mail.comНайти УолшаОстин2026-02-11
103cara.li@mail.comДорогая ЛиОстин2026-01-15
107gus.m@mail.comГас МоранХьюстон2026-02-19
108hana.s@mail.comХана СатоДаллас2026-02-25
105ella.r@mail.comЭлла РейесФорт-Уэрт2026-03-01
109ivan.k@mail.comИван КоваОстин2026-03-06
103cara.li@mail.comДорогая ЛиОстин2026-01-15
110jo.b@mail.comДа БрандтХьюстон2026-03-14
111ben.ortiz@mail.comБенджамин ОртисДаллас2026-03-20

1. Решите, что считать дубликатом, прежде чем писать что-либо.

Перед пояснением: клиент 103 появляется три раза, причем все столбцы идентичны. Клиент 105 появляется дважды с двумя разными городами. Электронная почта Бена Ортиса отображается под двумя разными идентификаторами. Какие из этих трех являются дубликатами?

На этот вопрос нет однозначного ответа, и это настоящий первый шаг в любой охоте за дубликатами. «Дубликат» не является свойством данных. Вы принимаете решение о том, какие столбцы должны совпадать, прежде чем две строки будут означать одно и то же. Есть три общих ответа, и каждый из них ведет к разным запросам и разным процедурам очистки.

Итак, развилка: какие столбцы определяют «то же самое»? Все это означает, что вы охотитесь за несчастными случаями при погрузке. Естественный ключ означает, что вы ищете противоречивые версии. Ни то, ни другое не означает, что вы можете охотиться на людей, и это другой инструмент. Все, что ниже, работает для первых двух, а запросы отличаются только тем, что вы ставите после GROUP BY.

2. Десятисекундный тест на любом столе

Прежде чем объяснить: не оглядываясь на таблицу, как бы вы получили одно число, которое говорит о том, появляется ли какой-либо идентификатор дважды?

Посчитайте ряды двумя способами. COUNT(*) считает каждую строку. COUNT(DISTINCT customer_id) подсчитывает, сколько существует различных значений идентификаторов. Если каждый идентификатор появляется один раз, два числа совпадают. Любой разрыв между ними — это количество дополнительных копий.

SELECT COUNT(*)                    AS total_rows,
       COUNT(DISTINCT customer_id) AS distinct_ids
FROM customers;
всего_строкразличные_иды
1411

Четырнадцать строк, одиннадцать различных идентификаторов, поэтому три строки — это дополнительные копии чего-то. Это запрос, который я выполняю для каждой таблицы, которую мне передают, перед любым соединением и перед любым отчетом, потому что он занимает около десяти секунд и меняет то, чему я доверяю. Он не сообщает вам, какие строки являются копиями. Оно говорит вам, нужна ли охота вообще, а 14 против 11 говорят, что да.

Особенно запустите тест на ключе соединения. Если вы собираетесь присоединиться к customer_id и в этой таблице 14 строк для 11 идентификаторов, соединение будет умножать строки, и каждый последующий счетчик будет совершенно неправильным. Десять секунд здесь экономят день там.

3. Перечислите дубликаты с помощью GROUP BY и HAVING.

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

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

SELECT customer_id, COUNT(*) AS copies
FROM customers
GROUP BY customer_id
HAVING COUNT(*) > 1
ORDER BY copies DESC;
customer_idкопии
1033
1052

Два покупателя, пять рядов между ними, трое из них статисты. Это как раз и объясняет пробел на втором этапе: 14 строк минус 11 идентификаторов — это 3, и вот они, две дополнительные копии 103 плюс одна дополнительная копия 105.

Чтобы вместо этого искать дубликаты всей строки, поместите каждый столбец в GROUP BY. Теперь сегмент формируется только тогда, когда строки совпадают во всем.

SELECT customer_id, email, full_name, city, signup_date,
       COUNT(*) AS copies
FROM customers
GROUP BY customer_id, email, full_name, city, signup_date
HAVING COUNT(*) > 1;
customer_idэлектронная почтаполное имяГородДата_регистрациикопии
103cara.li@mail.comДорогая ЛиОстин2026-01-153

Обратите внимание, что изменилось. Клиент 105 исчез из этого результата, поскольку ее две строки различаются по городам, поэтому они никогда не попадают в одну и ту же корзину. Прежде чем читать дальше, скажите своими словами, почему эти два запроса расходятся по поводу 105. Если вы можете это объяснить, у вас есть весь метод: GROUP BY список — это ваше определение дубликата, записанное в виде кода.

4. Просматривайте полные повторяющиеся строки, а не только сводку.

Перед пояснением: в сводке говорится, что у клиента 105 есть две копии, но чтобы решить, какую оставить, вам нужно увидеть обе строки рядом. В сгруппированном результате их невозможно отобразить, поскольку группировка их свернула. Как их вернуть?

Используйте сводку в качестве списка покупок. Сначала найдите идентификаторы с дубликатами, затем извлеките каждую строку, идентификатор которой есть в этом списке. IN это самый простой способ сказать это.

SELECT *
FROM customers
WHERE customer_id IN (
    SELECT customer_id
    FROM customers
    GROUP BY customer_id
    HAVING COUNT(*) > 1
)
ORDER BY customer_id, signup_date;
customer_idэлектронная почтаполное имяГородДата_регистрации
103cara.li@mail.comДорогая ЛиОстин2026-01-15
103cara.li@mail.comДорогая ЛиОстин2026-01-15
103cara.li@mail.comДорогая ЛиОстин2026-01-15
105ella.r@mail.comЭлла РейесДаллас2026-02-02
105ella.r@mail.comЭлла РейесФорт-Уэрт2026-03-01

Пять рядов, и теперь две проблемы выглядят совершенно разными. Клиент 103 — три одинаковые строки: авария при погрузке, нечего решать. Клиент 105 — это две версии одного человека: Даллас в феврале, Форт-Уэрт в марте, и кто-то должен сказать, какой город настоящий. Обратное соединение с той же сводкой дает идентичный результат, и я запустил оба варианта, чтобы убедиться: в любом случае одни и те же пять строк. Используйте то, что вам больше нравится; SQL-СОЕДИНЕНИЯ охватывает форму присоединения.

5. Отметьте хранителей и статистов с помощью ROW_NUMBER.

Перед пояснением: теперь вы можете видеть все пять повторяющихся строк. Какой столбец, добавленный в таблицу, позволит кому-либо еще действовать с ним, не переделывая вашу работу?

Номер копии. ROW_NUMBER() — это оконная функция, то есть она вычисляет значение для каждой строки, ничего не сворачивая. PARTITION BY customer_id перезапускает нумерацию для каждого клиента, и ORDER BY signup_date решает, кто получит номер один. Итак, копия номер 1 — ваш хранитель, а все, что выше 1, — лишнее, согласно записанному вами правилу.

SELECT customer_id, city, signup_date,
       ROW_NUMBER() OVER (
           PARTITION BY customer_id
           ORDER BY signup_date
       ) AS copy_number
FROM customers
ORDER BY customer_id, copy_number;

Дублированные клиенты возвращаются под таким номером.

customer_idГородДата_регистрацииномер_копии
103Остин2026-01-151
103Остин2026-01-152
103Остин2026-01-153
105Даллас2026-02-021
105Форт-Уэрт2026-03-012

Каждый второй клиент просто получает копию номер 1. Оберните ее в подзапрос и сохраните. copy_number > 1 и у вас есть ровно три дополнительных ряда, которые я проверил, чтобы подтвердить: два дополнительных ряда для 103 и ряд Форт-Уэрта для 105.

Вот ответвление варианта «сохранить против удаления», потому что это реальное решение. Удаление дополнительных данных делает таблицу чистой, но уничтожает улики, и если ваше правило хранителя было неверным, правильная строка исчезнет. При выборе дополнительных элементов сохраняется каждая строка и добавляется столбец, в котором указано, какую из них следует использовать в отчетах. Маркировка стоит одного столбца. Удаление стоит возможности изменить свое мнение. Вот почему работающие аналитики сначала отмечают и редко удаляют: фильтруют отчеты по copy_number = 1 и вы получите все преимущества удаления без какого-либо риска. Обратите внимание, что в запросе видно правило маркировки: для 105 ORDER BY signup_date сделал новый ряд в Форт-Уэрте дополнительным. Если компания говорит о новых победах, переверните заказ на signup_date DESC и вратарь тоже переворачивается. Правило представляет собой код, поэтому его можно просматривать и оспаривать.

Семейство оконных функций идет гораздо дальше этого трюка. Оконные функции проходит через него правильно. Если вам нужен узор на пальцах, а не в закладках, введите запрос самостоятельно несколько раз в SQL-тренировка.

6. Схема удаления, указанная один раз, с ограждением

Иногда ответ действительно заключается в удалении строк, обычно в случае аварийной загрузки всей строки, например, клиента 103. Вот шаблон, один раз. В SQLite каждая строка имеет скрытую rowid, поэтому оставьте самый маленький rowid на каждого клиента и удалите остальные. Другие базы данных используют ctid, идентификатор системы или ROW_NUMBER подзапрос, но форма та же.

DELETE FROM customers
WHERE rowid NOT IN (
    SELECT MIN(rowid)
    FROM customers
    GROUP BY customer_id
);

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

7. Полное до и после

Та же таблица, тот же вопрос: у каких клиентов есть повторяющиеся строки, и какая копия должна стоять?

До

SELECT customer_id, COUNT(*)
FROM customers
GROUP BY customer_id
HAVING COUNT(*) > 1;

Правильно, и недостаточно, чтобы действовать. Он называет номера 103 и 105, но не показывает ни одной из их строк, не указывает правила хранителя и не дает следующему человеку ничего для просмотра.

после

-- ============================================================
-- STEP 2: Which customer rows are duplicates, and which stands?
-- WHY: 14 rows but only 11 distinct customer_ids, so 3 rows
--      are extra copies. Reports must count customers once.
-- RULE: keeper = earliest signup_date per customer_id.
--      105's city conflict (Dallas vs Fort Worth) goes to the
--      data owner; nothing is deleted here.
-- ============================================================
--SELECT each customer row with its copy number:
--   ROW_NUMBER restarts at 1 for every customer_id,
--   ordered by signup_date, so copy_number 1 = keeper
SELECT customer_id, email, full_name, city, signup_date,
       ROW_NUMBER() OVER (
           PARTITION BY customer_id
           ORDER BY signup_date
       ) AS copy_number
FROM customers
ORDER BY customer_id, copy_number;

Присутствует каждая строка, записано правило хранителя, с которым рецензент может не согласиться, известный конфликт назван, а последующие запросы просто фильтруются, чтобы copy_number = 1. Формат комментариев тот же, что и у Как комментировать SQL, чтобы он обучал.

Теперь представьте, как выполняется запрос маркировки для вашей самой большой таблицы, секционированной по ее естественному ключу. Какой столбец вы бы поместили в ORDER BY принять решение о хранителе, и могли бы вы защитить этот выбор перед лицом, владеющим данными?

8. Крайние случаи, из-за которых поиск дубликатов идет не так, как надо

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

Пять, каждая из которых стоила кому-то реального времени.

Вы сгруппировали по неправильному определению. Это ответ с нулевыми строками. Группировка по каждому столбцу не находит дубликатов в таблице, где один и тот же идентификатор встречается дважды в разных городах, точно так же, как клиент 105. Когда запрос всей строки возвращается чистым, запустите версию с естественным ключом, прежде чем объявлять таблицу чистой.

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

Близкие совпадения ускользают от каждой точной группы. Конечные пробелы, заглавные буквы и варианты написания — это разные значения. GROUP BY. На моем столе специально стоит один из них: Бен Ортис и Бенджамин Ортис. Группировка по электронной почте выявляет эту пару, и я запустил ее: ben.ortiz@mail.com показывает 2 копии под 2 разными идентификаторами. Но группировка по имени никогда не поможет, и истинное нечеткое сопоставление Разрешение объекта, а не больший GROUP BY.

NULL группируются вместе. Две строки с отсутствующим адресом электронной почты попадают в одну корзину, поскольку при группировке все NULL-значения рассматриваются как одно значение. Ведро заготовок со счетом 40 – это не 40 дубликатов. В 40 строках отсутствует ключ, и это отдельная находка, о которой стоит сообщить отдельно.

Дубликаты, обнаруженные во время миграции, — это симптом, а не рутинная работа. При профилировании исходной системы до того, как миграция обнаружит дубликаты, подсчет свидетельствует о том, как вела себя старая система: повторные попытки сохранения, объединение офисов, пользователи, работающие над ошибкой. Запишите, откуда они взялись, прежде чем их кто-нибудь очистит, потому что шаблон предсказывает, что еще не так. Профилирование данных перед миграцией охватывает этот рабочий процесс.

Почему это работает

Метод опирается на одно свойство группировки. Группировка разбивает строки на непересекающиеся наборы по значению, поэтому две строки попадают в один и тот же набор точно тогда, когда они совпадают в каждом сгруппированном столбце. Это делает GROUP BY перечислите точное, осуществимое изложение вашего повторяющегося определения, а не расплывчатое намерение. Реляционная модель с самого начала рассматривала таблицы как наборы строк (Кодд, 1970, Коммуникации ACM, 13(6), 377–387), поэтому именно точное совпадение — это все, что база данных дает вам изначально, и почему для определения одного и того же человека с разным написанием нужны отдельные инструменты.

Привычка ставить отметки имеет более простое оправдание: она делает каждое решение обратимым. Правило хранителя живет в ORDER BY что рецензент может читать и перелистывать, а дополнительные материалы остаются на диске до тех пор, пока кто-то, обладающий властью над данными, не скажет иное. Удаление незаметно внедряет одно и то же правило и обеспечивает его постоянное применение.

Есть также причина, по которой эта страница продолжает просить вас ответить, прежде чем она объяснит. Попытка ответить до того, как вы увидите решение, значительно улучшит то, что вы сохраните, даже если ваша попытка окажется ошибочной (Бисра, Лю, Несбит, Салими и Винн, 2018, Обзор педагогической психологии, 30(3), 703–725), а практика извлечения информации превосходит перечитывание того же материала (Краснее & выбирать карпа, 2006, Психологическая наука, 17(3), 249–255). Решение о том, считать ли клиента 105 дубликатом (прежде чем я вам сказал, это зависит от того, почему три определения вернутся к вам на клавиатуре).

Использование этого в вашем собственном проекте

Проверка каждой вашей таблицы на наличие дубликатов за один присест — это утомительно, и вы оставите ее за четвертым столом. Вместо этого сделайте это по порядку.

  1. Запустите десятисекундный тест на одной таблице, от которой больше всего зависит ваш текущий отчет. COUNT(*) против COUNT(DISTINCT key). Если они совпадают, остановитесь. У вас есть ответ на сегодня.
  2. Если они различаются, запишите повторяющееся определение в одном предложении. прежде чем прикасаться к клавиатуре. «Один и тот же идентификатор клиента» и «идентичная строка» приводят к разным запросам, и если вы выберете один из них вслух, вы не сможете их смешивать.
  3. Перечислите правонарушителей с помощью GROUP BY и HAVING, затем вытащите полные строки назад с помощью IN. Прочтите их. Сами строки обычно сообщают вам, какая из трех проблем у вас возникла.
  4. Отметьте хранителей ROW_NUMBER и письменным правилом. Отфильтруйте отчеты по копии номер 1. Ничего не удаляйте на этой неделе.
  5. Передайте конфликтные строки тому, кто владеет данными. Два города Эллы Рейес не являются проблемой запроса, и если вы сделаете вид, что это не так, вы просто переместите ошибку в ваш отчет.

Если у вас есть бумага поблизости и у вас есть пять минут, один дополнительный рисунок зафиксирует метод. Нарисуйте восемь строк, дайте трем из них общую отметку, сверните строки в сегменты и напишите рядом с ними количество ведер. Обведите ведра над одним. Перерисовка этого по памяти — честная проверка того, владеете ли вы этой идеей.

Подробнее об этом и еще что-нибудь подобное. Все инструкции находятся в одном месте на индекс направляющих: SQL, Tableau, миграция данных и связанные с ними рабочие привычки.

Все на одном экране

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

концепцияЧто он делает
Первый шагРешите, какие столбцы определяют «то же самое». Это решение выбирает запрос.
Дубликат всей строкиКаждый столбец соответствует. Обычно это авария при погрузке. Безопасно удалить после маркировки.
Дубликат с естественным ключомТот же реальный идентификатор, другие столбцы отличаются. Кто-то должен выбрать истинную версию.
Тот же человек, разное написаниеТочного совпадения не существует. Это разрешение сущностей, отдельный инструмент.
Десятисекундный тестCOUNT(*) против COUNT(DISTINCT key). Любой пробел – это количество доп.
Список дубликатовGROUP BY key HAVING COUNT(*) > 1. Список GROUP BY — это ваше определение.
Посмотреть полные строкиWHERE key IN (the summary), или присоединитесь к нему снова. В любом случае одни и те же строки.
Хранители отметокROW_NUMBER() OVER (PARTITION BY key ORDER BY rule). Копия 1 сохраняется, копия выше 1 является дополнительной.
Зачем отмечать, а не удалятьМаркировка двусторонняя, и правило хранителя остается видимым. Удаление — ни то, ни другое.
Шаблон удаленияДержать MIN(rowid) за ключ, остальные удалите. SELECT его первым. Сохраните исходную таблицу.
Дубликатов не найденоПрежде чем объявить таблицу чистой, проверьте другое определение.
В любом случае сообщите о двойном учетеПроверьте клавишу соединения с обеих сторон. Объединяет несколько строк, каждая из которых выглядит чистой по отдельности.
НУЛЕВЫЕ ключиВсе значения NULL делят одну корзину. В этом подсчете отсутствуют данные, а не дубликаты.
Во время миграцииДубликаты являются свидетельством исходной системы. Зарегистрируйте их перед очисткой.
Единственная привычка, которую следует сохранить. Если вы больше ничего не берете с этой страницы, запустите COUNT(*) против COUNT(DISTINCT key) на каждой таблице, прежде чем доверять числу из нее. Десять секунд, и именно в этом разница между поиском дубликатов и тем, чтобы их нашел ваш заинтересованный человек. Если поиск дубликатов дает сбой, который не описан на этой странице, существует общая шлейф диагностики на предмет застревания.

Еще одна мысль, и мне бы искренне хотелось услышать ответы других людей. Моим любимым дубликатом, когда-либо найденным, был клиент, который существовал дважды, потому что оба офиса клялись, что зарегистрировали ее первым. Какова самая странная причина появления дубликата в ваших данных, и нашел ли его запрос или это сделал человек?

Ссылки

Количество строк слишком велико, и каждая строка выглядит на экране уникально.

SQL для аналитиков составляет 458 страниц, GROUP BY и HAVING используются для поиска того, что дублируется, прежде чем оно достигнет общего количества.

SQL для аналитиков, 19 $ →
Потренируйтесь на реальных таблицах перед собой.

тот Курс SQL запускает SQLite в браузере, поэтому вы можете заполнять дубликаты, выслеживать их и наблюдать за изменением количества по ходу работы. Рядом с ним лежат рабочие примеры, карточки и пробный экзамен. Ничего устанавливать, учетная запись не требуется.

Откройте набор SQL →