はじめに
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;
-
DISTINCT
は SELECT句で指定した列の組み合わせに対して重複を取り除きます。 - 内部的にはソートやハッシュ処理を使って重複判定するため、データ量が多いとコストがかかりやすい点に注意。
複数列の例
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
で同じキーをまとめ、COUNT
・SUM
・AVG
などの集計関数を使ってまとめた結果を得る。
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 が書けるようになります。