コマンド道場

DISTINCT の使い方

同じ内容の行が 1 つにまとまる

shain社員

busho_id
1
2
2
2
2
5

SELECT DISTINCT busho_id重複を除いた結果

busho_id
1
2
5

SELECT DISTINCT busho_id

  • 取り消し線= 結果に出てこない行
  • 社員 18 行 → 重複を除いた結果 711 行が消えます)

取り消し線は「すでに同じ内容の行が出ているので、結果に出てこない行」です。値そのものが消えるわけではありません。

DISTINCTまったく同じ行を 1 つにまとめます。よくある誤解は「列ごとに効く」というもので、実際は並べた列の組み合わせに対して効きます。だから列を 1 つ足すと、結果の行数はむしろ増えることがあります。

主要 7 製品で通用 そのまま使える: PostgreSQL / MySQL / MariaDB / SQLite / SQL Server / Oracle Database / IBM Db2

製品ごとの対応表(実測)を見る

このページの実行結果と図は、すべて本物のデータベースに同じ SQL を流して得たものです。手で書いた「たぶんこうなる」ではありません。

01重複した行を 1 つにまとめる

結論: `DISTINCT` は `SELECT` の直後に 1 回だけ書きます。

列名の前に個別に付けるものではありません。SELECT DISTINCT a, b と書けば、ab組み合わせが同じ行がまとめられます。

何行に減ったかは、付ける前と後を見比べるのが確実です。減らないときは、たいてい余計な列が混ざっています

1 行ずつ、何をしているか
  1. SELECT DISTINCT行全体の重複を消す
  2. busho_idこの列の組み合わせで判定する
  3. FROM shainどの表を見るか
  4. ORDER BY busho_id並び順

DISTINCT は列ごとではなく、SELECT に並べた列全体に効きます。

そのまま出すと社員の数だけ行が出る
SELECT busho_id FROM shain ORDER BY busho_id NULLS LAST;
busho_id
1
2
2
2
2
3
3
3

18 … うち先頭 8 行を表示

18 行返ります。同じ部署番号が何度も現れます。

⚠️ この書き方は MySQL / MariaDB / SQL Server では使えません(ORDER BY ... NULLS LAST)。製品ごとの対応表

重複を除く
SELECT DISTINCT busho_id FROM shain ORDER BY busho_id NULLS LAST;
busho_id
1
2
3
4
5
7
NULL

7

社員が実際にいる部署の番号と、部署なし(NULL)が 1 行ずつになりました。

⚠️ この書き方は MySQL / MariaDB / SQL Server では使えません(ORDER BY ... NULLS LAST)。製品ごとの対応表

02⚠️ 列を足すと行が増えることがある

結論: `DISTINCT` は「並べた列の組み合わせ」で判定します。

列を 1 つ足すと、組み合わせの種類が増えます。その結果、行数は減るどころか増えることがあります

DISTINCT を付けたのに行が減らない」というときは、まず余計な列が混ざっていないかを疑ってください。とくに主キーや ID を混ぜると、すべての行が別物になり、DISTINCT はまったく効きません。

1 列だけで判定する
SELECT DISTINCT busho_id FROM shain ORDER BY busho_id NULLS LAST;
busho_id
1
2
3
4
5
7
NULL

7

部署の種類ぶんだけの行になります。

⚠️ この書き方は MySQL / MariaDB / SQL Server では使えません(ORDER BY ... NULLS LAST)。製品ごとの対応表

列を 1 つ足す
SELECT DISTINCT busho_id, joushi_id FROM shain ORDER BY busho_id NULLS LAST, joushi_id NULLS LAST;
busho_idjoushi_id
1NULL
21
22
31
38
41
414
52
56
78
712
NULL1

12

「部署と上司の組み合わせ」の種類になるので、行数が増えました。

⚠️ この書き方は MySQL / MariaDB / SQL Server では使えません(ORDER BY ... NULLS LAST)。製品ごとの対応表

⚠️ 主キーを混ぜると効かなくなる
SELECT DISTINCT shain_id, busho_id FROM shain ORDER BY shain_id;
shain_idbusho_id
11
22
32
42

18 … うち先頭 4 行を表示

shain_id は全行で違うので、組み合わせも全行で違います。18 行のまま、DISTINCT はまったく効いていません。

03NULL どうしはまとまる

結論: `DISTINCT` は NULL どうしを「同じ」として 1 つにまとめます。

比較のときは NULL = NULL が真になりません。それなのに DISTINCT ではまとまるのは、重複の判定が「値の比較」ではなく「同じかどうかの判定」だからです。同じ理由で GROUP BY も NULL をひとまとめにします。

NULL を結果から外したいなら、WHERE 列 IS NOT NULL で明示的に落とします。

NULL の行が何行あるか
SELECT count(*) AS gyosu, count(busho_id) AS atai_ari FROM shain;
gyosuatai_ari
1816

1

差の 2 行が、部署が決まっていない社員です。

NULL を除いてから重複を消す
SELECT DISTINCT busho_id FROM shain WHERE busho_id IS NOT NULL ORDER BY busho_id;
busho_id
1
2
3
4
5
7

6

NULL の行が 1 行にまとまるのではなく、そもそも結果から消えました。

04⚠️ 結合の重複を DISTINCT で隠さない

結論: `DISTINCT` は重複の「原因」を直しません。結果を上塗りするだけです。

結合で行が増えるのは、たいてい結合の条件が足りないか、1 対多の関係を見落としているからです。そこに DISTINCT を付けると行数は合いますが、原因は残ったままです。

とくに集計と組み合わせると危険です。重複した行を数えたり足したりしたあとで DISTINCT を付けても、合計はもう狂っています。行が増えたときは、まず結合の形を疑ってください。

DISTINCT を書きたくなったら、「なぜ重複しているのか」を先に説明できるか自問すると、たいてい設計の抜けが見つかります。

1 対多の結合では部署名が繰り返される
SELECT b.busho_name, s.name FROM busho b JOIN shain s ON s.busho_id = b.busho_id ORDER BY b.busho_id, s.shain_id;
busho_namename
経営鈴木
開発佐藤
開発田中
開発中村
開発小林
営業山田

16 … うち先頭 6 行を表示

部署は 1 行しか無いのに、社員の数だけ繰り返されています。これは重複ではなく、正しい 1 対多の結果です。

部署名だけが欲しいなら DISTINCT でよい
SELECT DISTINCT b.busho_name FROM busho b JOIN shain s ON s.busho_id = b.busho_id ORDER BY b.busho_name;
busho_name
マーケ
営業
基盤
管理
経営
開発

6

「社員がいる部署の一覧」が欲しいのなら、これは正しい使い方です。原因を隠しているわけではありません。

数えるときは集約側で重複を除く
SELECT count(DISTINCT b.busho_name) AS shurui FROM busho b JOIN shain s ON s.busho_id = b.busho_id;
shurui
6

1

行が増えた状態で count(*) を書くと社員数になってしまいます。何を数えているのかを毎回言葉にしてください。

05数えるときは count(DISTINCT 列)

結論: 「何種類あるか」を数えるなら `count(DISTINCT 列)` です。

count(列) は重複を除きません。同じ値が何度出てきても、そのぶん数えます。延べ件数と種類数はまったく別のものです。

どちらも数字が出るぶん、取り違えてもエラーになりません。「何を 1 と数えるのか」を言葉にしてから書いてください。

行数・延べ・種類数を並べる
SELECT count(*) AS gyosu, count(busho_id) AS nobe, count(DISTINCT busho_id) AS shurui FROM shain;
gyosunobeshurui
18166

1

3 つとも違う数字です。count(DISTINCT 列) だけが「種類」を数えています。

派生表で数えても同じ
SELECT count(*) AS shurui FROM (SELECT DISTINCT busho_id FROM shain) t;
shurui
7

1

こちらは NULL も 1 種類として数えるため、count(DISTINCT 列) より 1 多くなります。集約関数は NULL を数えないためです。

自分で打ってみる

このページの例と同じデータが入った本物のデータベースを、この場(あなたのブラウザの中)で起動できます。打った SQL がサーバーへ送られることはありません。

押すと初回だけ約 9MB を読み込みます(2 回目以降はキャッシュされます)。

解いてみる

読んだだけでは書けるようになりません。実行結果で採点します。

関連するトピック

  • GROUP BY 句まとめるという点は似ていますが、GROUP BY は集計値を一緒に出せます。
  • 集約関数count(DISTINCT 列) と count(列) の違いはここで扱っています。
  • IS NULLNULL どうしがまとまる理由は、NULL の扱いを知ると腑に落ちます。

根拠(一次情報)