0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SQLで使いこなす!DISTINCT/GROUP BY/EXISTS の使い分け

Last updated at Posted at 2025-09-30

はじめに

SQLを書いていると、「重複を消したい」「集計したい」「条件付きで存在チェックしたい」など、似たような要件が登場します。
ですが、目的が異なる処理を同じ書き方で済ませようとすると、読みづらさ・パフォーマンス低下・バグの温床になることも。

本記事では、DISTINCT, GROUP BY, EXISTS の三者を比較しつつ、それぞれの使いどころ・注意点を整理します。
サンプルコードを交えながら、実践で使える知見も盛り込みます。


サンプルスキーマ

以下のようなシンプルなスキーマを前提に話を進めます。

-- 顧客テーブル
customers (
  id           INT PRIMARY KEY,
  name         VARCHAR,
  city         VARCHAR
);

-- 注文テーブル
orders (
  id             INT PRIMARY KEY,
  customer_id    INT REFERENCES customers(id),
  order_date     DATE,
  total_amount   NUMERIC
);

-- 注文明細テーブル
order_items (
  id         INT PRIMARY KEY,
  order_id   INT REFERENCES orders(id),
  product_id INT,
  quantity   INT,
  unit_price NUMERIC
);

1. DISTINCT — 重複を排除して「一覧」を得たいとき

基本

SELECT DISTINCT city
FROM customers;
  • DISTINCTSELECT句で指定した列の組み合わせに対して重複を取り除きます。
  • 内部的にはソートやハッシュ処理を使って重複判定するため、データ量が多いとコストがかかりやすい点に注意。

複数列の例

SELECT DISTINCT city, name
FROM customers;
  • この場合、 (city, name) の組み合わせが重複しないように結果が返る。
  • 列を増やすほど「重複判定される可能性が下がる = 除去される重複が少なくなる」

COUNT(DISTINCT)

SELECT COUNT(DISTINCT city)
FROM customers;
  • 都市のユニークな数を出す。
  • 注意:複数列の COUNT(DISTINCT col1, col2) は DB によって対応状況が異なります。

2. GROUP BY — グループ化して集計をする

基本

SELECT city, COUNT(*) AS customer_count
FROM customers
GROUP BY city
ORDER BY customer_count DESC;
  • GROUP BY で同じキーをまとめ、COUNTSUMAVG などの集計関数を使ってまとめた結果を得る。

HAVING を使ったフィルタリング

SELECT city, COUNT(*) AS customer_count
FROM customers
GROUP BY city
HAVING COUNT(*) >= 5;
  • WHERE集計前の行 に対する絞り込み用。
  • HAVING集計後のグループ に対する条件。

複数列でグルーピング

SELECT city, name, COUNT(*)
FROM customers
GROUP BY city, name;

3. EXISTS — サブクエリで「存在チェック」をしたいとき

基本

SELECT c.*
FROM customers c
WHERE EXISTS (
  SELECT 1
  FROM orders o
  WHERE o.customer_id = c.id
);
  • EXISTS は、サブクエリで “ひとつでも行が返れば真” と判断されます。
  • 行の中身は使われないため、通常 SELECT 1 が使われます。

NOT EXISTS と NULL の扱い

SELECT c.*
FROM customers c
WHERE NOT EXISTS (
  SELECT 1
  FROM orders o
  WHERE o.customer_id = c.id
);
  • NOT IN と違って、サブクエリ結果側に NULL が混ざっていても誤判定されにくい。
    NOT EXISTS の方が安全なことが多い。

EXISTS vs IN vs JOIN

  • EXISTS:存在チェックが目的で、重複を気にしないならこちら。
  • IN:静的リストとの包含関係を使いたいとき。ただし NULL に弱い。
  • JOIN:複数テーブルの列データを組み合わせて取りたいとき。存在チェックだけなら冗長になりがち。

4. パフォーマンス/インデックス設計のヒント

構文 最適化ポイント
DISTINCT 抽出列はできるだけ絞る。WHERE で事前絞り込み。
GROUP BY 集計キーの選定を慎重に。中間データ爆発を避ける。部分集計 → 再集計も検討。
EXISTS サブクエリ側の結合条件列にインデックスを貼る。NOT EXISTS は反結合最適化が効く DB が多い。

5. アンチパターンに要注意

  • JOIN→DISTINCT で重複排除
    → 存在チェック目的なら EXISTS に書き換えた方が明快かつ高速。

  • SELECT に不要な列を入れてから DISTINCT
    → 重複判定が効かなくなる原因になる。

  • NOT IN による除外処理
    → NULL 混在時に思わぬ挙動をするので、NOT EXISTS の方が安全。


まとめ:3構文の使い分け

  • DISTINCT:結果の重複を除きたいとき
  • GROUP BY:集計したいとき
  • EXISTS / NOT EXISTS:関連テーブルの存在有無を確認したいとき

この三つを使い分けることで、読みやすく意図がはっきりした SQL が書けるようになります。

0
0
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
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?