重複行を除外 - DISTINCT
結果から重複行を排除する。DISTINCT ON など PostgreSQL 拡張や GROUP BY との使い分けも解説
概念図
構文
sql
SELECT DISTINCT 列1, 列2, ... FROM テーブルサンプル
users テーブルに登場する国コードをユニークに取り出す
sql
SELECT DISTINCT country
FROM users
ORDER BY country;DISTINCT の意味
SELECT DISTINCT は 選んだ全列の組み合わせが完全一致する行を 1 行に畳み込みます。1 列にかかっているように見えても、実際は SELECT リスト全体がキーです。
NULL 同士は等値ではありませんが、DISTINCT では 同じ NULL 扱いでまとめられます(これは通常の等値比較と違う特殊な挙動)。
DISTINCT vs GROUP BY
集計せず重複だけ消したいなら DISTINCT が読みやすく、集計を伴うなら GROUP BY が自然です。
- 性能は多くの RDBMS で同等(内部的にハッシュ or ソートで集約)
DISTINCTは「意図 = 重複排除」を宣言できるので読み手に親切GROUP BYはHAVINGや集約関数と組み合わせられる
DISTINCT ON (PostgreSQL 拡張)
PostgreSQL 独自の DISTINCT ON (key) は 指定したキーごとに ORDER BY の先頭 1 行を返す便利な書き方です。ユーザーごとの最新注文、カテゴリごとのベストセラーなど「グループ内 Top-1」に強力。
他 RDBMS では ROW_NUMBER() ウィンドウ関数で同じ結果を得ます。
sql
-- PostgreSQL: ユーザーごとの最新注文
SELECT DISTINCT ON (user_id) user_id, id, created_at
FROM orders
ORDER BY user_id, created_at DESC;
-- 他 RDBMS (標準 SQL)
SELECT user_id, id, created_at
FROM (
SELECT o.*, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) AS rn
FROM orders o
) t
WHERE rn = 1;落とし穴: SELECT * に DISTINCT を付ける
SELECT DISTINCT * は全列を比較対象にしてしまい、ほぼ重複を消せないまま全列ソートのコストだけ払う、というアンチパターンになりがちです。本当にユニーク化したいキーだけを SELECT に並べるのが定石です。
また、ORDER BY に SELECT リストにない列を使うと多くの RDBMS でエラーになります(DISTINCT は SELECT 列だけで重複判定するため)。
