「SQLで重複データを見つけたい」
「ユニーク制約を付ける前に既存データを点検したい」
「JOIN後にレコードが想定以上に増えた原因を調べたい」
このような悩みは、データベースを扱う現場で非常によく発生します。
特に、
- データ移行・CSV取り込み時の品質チェック
- 顧客マスタ・会員情報のクレンジング
- 分析用データの前処理
- ユニークインデックス追加前の事前検証
といった場面では、「重複レコードの正確な抽出」は必須スキルです。
本記事では、以下を体系的に解説します。
- 重複の正しい定義の決め方
GROUP BY + HAVINGによる重複キー抽出
- 重複行を全件取得する方法(サブクエリ・自己結合)
- 複数列(複合キー)の重複抽出
- 大量データでのパフォーマンス最適化
- 安全な削除・除外の考え方
基礎から実務レベルまで、網羅的に解説します。
重複レコードとは何か?まず定義を決める
重複抽出で最も重要なのは「定義」
SQLを書く前に、必ず明確にすべきことがあります。
何をもって「重複」とするのか?
例えば:
- メールアドレスが同じなら重複?
- 電話番号+都道府県の組み合わせが同じなら重複?
- NULLは重複として扱う?
ここが曖昧なままクエリを書くと、正しいSQLでも「欲しい結果にならない」状態になります。
単一列の重複
例:
- email が同じ
- phone_number が同じ
この場合、GROUP BY email のように1列だけを基準にします。
複数列(複合キー)の重複
例:
- email + service_type
- product_id + shop_id
- name + birthday
この場合は:
GROUP BY email, service_type
のように複数列を指定します。
業務上「一意であるべき単位」を明文化することが重要です。
キーだけ欲しいのか?行全体が必要か?
重複抽出には2種類あります。
| 目的 |
取得対象 |
| 重複値の確認 |
キーのみ |
| 修正・削除対象の特定 |
行全体 |
用途によりSQLが変わります。
重複キーだけを抽出する(GROUP BY + HAVING)
基本構文
SELECT phone_number, COUNT(*) AS duplicate_count
FROM customer
GROUP BY phone_number
HAVING COUNT(*) > 1;
解説
GROUP BY でキーごとに集計
COUNT(*) で件数を算出
HAVING で2件以上を抽出
これが重複抽出の基本パターンです。
COUNT(*) と COUNT(列) の違い
| 書き方 |
NULLを数えるか |
| COUNT(*) |
数える |
| COUNT(column) |
数えない |
NULLをどう扱うかは必ず要件で確認します。
重複件数順に並べる
ORDER BY duplicate_count DESC;
実務では「多い順」に見るのが効率的です。
重複している行を全件取得する方法
キーだけでは削除・修正はできません。
元の行をすべて取得する方法を解説します。
方法①:サブクエリ(IN)
SELECT *
FROM customer
WHERE phone_number IN (
SELECT phone_number
FROM customer
GROUP BY phone_number
HAVING COUNT(*) > 1
)
ORDER BY phone_number, id;
特徴
- 分かりやすい
- 調査用途に最適
- 初心者にも扱いやすい
方法②:自己結合(JOIN)
SELECT c.*, d.duplicate_count
FROM customer c
JOIN (
SELECT phone_number, COUNT(*) AS duplicate_count
FROM customer
GROUP BY phone_number
HAVING COUNT(*) > 1
) d
ON c.phone_number = d.phone_number
ORDER BY c.phone_number, c.updated_at DESC;
特徴
複数列の重複抽出
SELECT email, service_type, COUNT(*) AS duplicate_count
FROM users
GROUP BY email, service_type
HAVING COUNT(*) > 1;
考え方は単一列と同じです。
列を増やすと重複は減り、減らすと重複は増えます。
入力ゆれ対策(実務で必須)
重複の原因の多くは入力ゆれです。
例:
GROUP BY LOWER(TRIM(email))
ただし関数を使うとインデックスが効かなくなる可能性があります。
対策:
- 取り込み時に正規化
- 正規化済みカラムを持つ
- アプリ側で統一
大量データでの注意点
重複抽出は高負荷になりやすい処理です。
インデックスの確認
キー列にインデックスがあるか確認。
実行計画の確認
- MySQL:
EXPLAIN
- PostgreSQL:
EXPLAIN ANALYZE
- SQL Server: 実行プラン表示
公式ドキュメントも参考になります:
範囲を絞って検証
いきなり全期間で実行しない。
WHERE created_at >= '2026-01-01'
段階的に確認するのが安全です。
重複の除外・最大以外を除外する方法
最新だけ残す(ウィンドウ関数)
SELECT *
FROM (
SELECT *,
ROW_NUMBER() OVER (
PARTITION BY phone_number
ORDER BY updated_at DESC
) AS rn
FROM customer
) t
WHERE rn = 1;
ウィンドウ関数は実務で非常に強力です。
削除の考え方(重要)
削除前に必ず:
-
SELECTで対象確認
-
件数確認
-
バックアップ取得
-
トランザクション内で実行
重複原因が取り込みロジックなら、削除だけでは再発します。
よくある失敗パターン
① DISTINCTで重複抽出しようとする
DISTINCT はユニーク一覧を作るものであり、重複検知には不向き。
② 重複定義が曖昧
SQLの問題ではなく要件定義の問題。
③ インデックス無視
関数適用でインデックスが無効化される。
④ 本番データでいきなりDELETE
事故の典型パターン。
まとめ
SQLで重複レコードを抽出する流れは次の通りです。
-
重複の定義を明確にする
-
GROUP BY + HAVING でキー抽出
-
サブクエリ or JOINで行全件取得
-
複数列重複にも対応
-
実行計画とインデックス確認
-
安全な削除手順を踏む
この型を持っておけば、どのRDBMSでも応用できます。
次のステップへ
データ品質の担保やパフォーマンス改善に強いエンジニアは、どの企業でも重宝されます。
私たちは、
- SQL最適化
- データ設計
- 大規模データ分析基盤構築
- パフォーマンスチューニング
といった分野に強みを持つ技術チームです。
本記事の内容に強い関心を持った方、
「より高度なデータ設計・パフォーマンス最適化に挑戦したい」と感じた方へ。
現在、データベース・バックエンド領域を一緒に深めていく仲間を募集しています。
技術に真剣に向き合える環境で、
あなたのSQLスキルを次のレベルへ引き上げてみませんか。