DISTINCT の使い方
shain社員
| busho_id |
|---|
| 1 |
| 2 |
| 2✕ |
| 2✕ |
| 2✕ |
| 5 |
| … |
SELECT DISTINCT busho_id重複を除いた結果
| busho_id |
|---|
| 1 |
| 2 |
| 5 |
| … |
SELECT DISTINCT busho_id
- 取り消し線= 結果に出てこない行
- 社員 18 行 → 重複を除いた結果 7 行(11 行が消えます)
取り消し線は「すでに同じ内容の行が出ているので、結果に出てこない行」です。値そのものが消えるわけではありません。
DISTINCT はまったく同じ行を 1 つにまとめます。よくある誤解は「列ごとに効く」というもので、実際は並べた列の組み合わせに対して効きます。だから列を 1 つ足すと、結果の行数はむしろ増えることがあります。
主要 7 製品で通用 そのまま使える: PostgreSQL / MySQL / MariaDB / SQLite / SQL Server / Oracle Database / IBM Db2
このページの実行結果と図は、すべて本物のデータベースに同じ SQL を流して得たものです。手で書いた「たぶんこうなる」ではありません。
01重複した行を 1 つにまとめる
結論: `DISTINCT` は `SELECT` の直後に 1 回だけ書きます。
列名の前に個別に付けるものではありません。SELECT DISTINCT a, b と書けば、a と b の組み合わせが同じ行がまとめられます。
何行に減ったかは、付ける前と後を見比べるのが確実です。減らないときは、たいてい余計な列が混ざっています。
SELECT DISTINCT行全体の重複を消すbusho_idこの列の組み合わせで判定するFROM shainどの表を見るか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 はまったく効きません。
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)。製品ごとの対応表
SELECT DISTINCT busho_id, joushi_id FROM shain ORDER BY busho_id NULLS LAST, joushi_id NULLS LAST;| busho_id | joushi_id |
|---|---|
| 1 | NULL |
| 2 | 1 |
| 2 | 2 |
| 3 | 1 |
| 3 | 8 |
| 4 | 1 |
| 4 | 14 |
| 5 | 2 |
| 5 | 6 |
| 7 | 8 |
| 7 | 12 |
| NULL | 1 |
(12 行)
「部署と上司の組み合わせ」の種類になるので、行数が増えました。
⚠️ この書き方は MySQL / MariaDB / SQL Server では使えません(ORDER BY ... NULLS LAST)。製品ごとの対応表
SELECT DISTINCT shain_id, busho_id FROM shain ORDER BY shain_id;| shain_id | busho_id |
|---|---|
| 1 | 1 |
| 2 | 2 |
| 3 | 2 |
| 4 | 2 |
(18 行 … うち先頭 4 行を表示)
shain_id は全行で違うので、組み合わせも全行で違います。18 行のまま、DISTINCT はまったく効いていません。
03NULL どうしはまとまる
結論: `DISTINCT` は NULL どうしを「同じ」として 1 つにまとめます。
比較のときは NULL = NULL が真になりません。それなのに DISTINCT ではまとまるのは、重複の判定が「値の比較」ではなく「同じかどうかの判定」だからです。同じ理由で GROUP BY も NULL をひとまとめにします。
NULL を結果から外したいなら、WHERE 列 IS NOT NULL で明示的に落とします。
SELECT count(*) AS gyosu, count(busho_id) AS atai_ari FROM shain;| gyosu | atai_ari |
|---|---|
| 18 | 16 |
(1 行)
差の 2 行が、部署が決まっていない社員です。
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 を書きたくなったら、「なぜ重複しているのか」を先に説明できるか自問すると、たいてい設計の抜けが見つかります。
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_name | name |
|---|---|
| 経営 | 鈴木 |
| 開発 | 佐藤 |
| 開発 | 田中 |
| 開発 | 中村 |
| 開発 | 小林 |
| 営業 | 山田 |
(16 行 … うち先頭 6 行を表示)
部署は 1 行しか無いのに、社員の数だけ繰り返されています。これは重複ではなく、正しい 1 対多の結果です。
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;| gyosu | nobe | shurui |
|---|---|---|
| 18 | 16 | 6 |
(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 の扱いを知ると腑に落ちます。
根拠(一次情報)
- PostgreSQL 18 マニュアル: SELECT(DISTINCT 句)一次情報・確認 2026-08-02
- PostgreSQL 18 マニュアル: 集約関数一次情報・確認 2026-08-02