こんにちは!
SQLでデータ抽出をしていると必ず出会う問題のひとつが「重複」です。
私自身も4月からSQLを触り始めましたが、この「重複」の対応には何度も頭を悩ませてきました。
そこで今回は、実際に問題解決につながった方法を紹介したいと思います。
1. 重複を確認する(GROUP BY + COUNT)
まずは「どの値が重複しているのか」を確認する方法です。
SELECT
product_name,
COUNT(*) AS cnt
FROM products
GROUP BY product_name;
結果↓
| product_name | cnt |
|---|---|
| リンゴ | 2 |
| バナナ | 1 |
| みかん | 1 |
cnt が 2 以上なら重複しています。
2. 重複を消す(DISTINCT)
例えば、お客さんの来店記録が以下のように入っているとします。
| 日付 | 店舗名 | 来店者ID | |
|---|---|---|---|
| 2025-08-01 | 東京店 | A001 | |
| 2025-08-01 | 東京店 | A001 | ← 重複 |
| 2025-08-01 | 大阪店 | B002 | |
| 2025-08-02 | 東京店 | A003 |
このまま COUNT(*) をすると、重複も含めて数えられてしまいます。
SELECT
store_name,
COUNT(*) AS cnt
FROM customers
GROUP BY store_name;
結果:
| 店舗名 | cnt | |
|---|---|---|
| 東京店 | 3 | ← 実際は 2 人だが重複のせいで 3 |
| 大阪店 | 1 |
これを解決するのが DISTINCT です。
SELECT
store_name,
COUNT(DISTINCT customer_id) AS cnt
FROM customers
GROUP BY store_name;
結果:
| 店舗名 | cnt | |
|---|---|---|
| 東京店 | 2 | ← 重複が除かれて正しい値 |
| 大阪店 | 1 |
➡DISTINCT は「同じ値を 1 つにまとめて数えたい」時に有効です。
3. 重複をまとめて集計する(SUM / MIN / MAX)
ここからは「重複を消す」のではなく、まとめて意味のある値にする方法です。
(1) 合計を出す:SUM()
実務でよくあるのが「店舗ごとの件数を日付単位で集計したい」というケース。
例えば、売上データと来客データを UNION で結合したとします。
SELECT date, store_name, 1 AS cnt FROM sales
UNION ALL
SELECT date, store_name, 1 AS cnt FROM visits;
このまま集計すると、同じ店舗が同じ日付で複数回カウントされてしまうことがあります。
そこで SUM() を使って、重複をまとめて合計します。
SELECT
date,
store_name,
SUM(cnt) AS total_count
FROM (
SELECT date, store_name, 1 AS cnt FROM sales
UNION ALL
SELECT date, store_name, 1 AS cnt FROM visits
) AS merged
GROUP BY date, store_name;
結果:
| date | store_name | total_count |
|---|---|---|
| 2025-08-01 | A店 | 5 |
| 2025-08-01 | B店 | 3 |
➡ SUM(cnt) を使うことで、同じ店舗・日付ごとの件数を正しく集計できます。
(2) 最大・最小をとる:MAX() / MIN()
もう一つよくあるのが「同じIDなのに名前が微妙に違う」というケースです。
store_id | store_name
---------|------------------
123 | 田中商店
123 | 田中商店株式会社
123 | 田中商店(本店)
456 | 山田ストア
456 | 山田ストア本店
このままでは、同じ店舗なのに複数行に分かれてしまい、分析に不便です。
DISTINCTでは不十分
つい DISTINCT を使いたくなりますが…
SELECT DISTINCT store_id, store_name
FROM store_table;
これは store_id と store_name の組み合わせが残るだけなので、結局は重複が消えません。
MAX + GROUP BYで1行にまとめる
そんなときは MAX() と GROUP BY を使います。
SELECT
store_id,
MAX(store_name) AS store_name
FROM store_table
GROUP BY store_id;
結果:
store_id | store_name
---------|------------------
123 | 田中商店株式会社
456 | 山田ストア本店
-
GROUP BY store_id→ IDごとにまとめる -
MAX(store_name)→ 辞書順で一番大きい名前を選ぶ
MINを使うパターン
一方で「辞書順で一番小さい名前を選ぶ」場合は MIN() を使えばOKです。
SELECT
store_id,
MIN(store_name) AS store_name
FROM store_table
GROUP BY store_id;
➡ポイント
-
DISTINCT→ 組み合わせごとに残るので行数は減らない -
MAX/MIN→ 必ず1行にできる(ルールを決めて残せる) - どちらを使うかは「最新を残す」「最古を残す」など目的次第
4. よくある間違い
-
DISTINCTとGROUP BYの違いを混同する
→ DISTINCT は「列をユニークにする」だけ。GROUP BY は「集計(SUM, COUNT など)」とセットで使う。 -
サブクエリで
ASを付け忘れる
→ サブクエリは必ず別名を付けないとエラーになる。 -
COUNT と SUM の混乱
→COUNT(*)は「行の数」。SUM()は「数値を合計」。用途が違うので要注意。
まとめ
SQLで重複データを扱うには目的によって方法が変わります。
-
重複を確認する →
GROUP BY + COUNT
(どの値が何回出てきているかを調べたいとき) -
ユニークな値だけ欲しい →
DISTINCT
(お客さんや商品を「かぶりなく一覧にしたい」とき) -
件数を正しく合計したい →
SUM()
(売上・来店など、複数テーブルをUNIONして日付×店舗ごとに集計したいとき) -
重複の中から代表値を残したい →
MIN()/MAX()
(同じIDに複数の名前がある場合 → 最古の名前を残すならMIN、最新や辞書順で大きい方を残すならMAX)
ポイントは「重複を消す」のか「重複をまとめる」のか。
これを意識すれば、SQLでやりたいことを正しく実行できると思います。