0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

DISTINCTだけじゃない!SQLで重複を消す&まとめる4つの方法

0
Last updated at Posted at 2025-09-11

こんにちは!

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. よくある間違い

  • DISTINCTGROUP BY の違いを混同する
    DISTINCT は「列をユニークにする」だけ。GROUP BY は「集計(SUM, COUNT など)」とセットで使う。

  • サブクエリで AS を付け忘れる
    → サブクエリは必ず別名を付けないとエラーになる。

  • COUNT と SUM の混乱
    COUNT(*) は「行の数」。SUM() は「数値を合計」。用途が違うので要注意。

まとめ

SQLで重複データを扱うには目的によって方法が変わります。

  • 重複を確認するGROUP BY + COUNT
    (どの値が何回出てきているかを調べたいとき)

  • ユニークな値だけ欲しいDISTINCT
    (お客さんや商品を「かぶりなく一覧にしたい」とき)

  • 件数を正しく合計したいSUM()
    (売上・来店など、複数テーブルを UNION して日付×店舗ごとに集計したいとき)

  • 重複の中から代表値を残したいMIN() / MAX()
    (同じIDに複数の名前がある場合 → 最古の名前を残すなら MIN、最新や辞書順で大きい方を残すなら MAX


ポイントは「重複を消す」のか「重複をまとめる」のか。
これを意識すれば、SQLでやりたいことを正しく実行できると思います。


0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?