Query Go
重複行を除外 - DISTINCT の使い方・オプション・サンプル

重複行を除外 - DISTINCT

結果から重複行を排除する。DISTINCT ON など PostgreSQL 拡張や GROUP BY との使い分けも解説

概念図

DISTINCT diagram

構文

sql
SELECT DISTINCT1, 列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 BYHAVING や集約関数と組み合わせられる

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 列だけで重複判定するため)。

関連トピック