相関副問い合わせ(外側の行ごとに計算する)の使い方
shain社員
| name | busho_id | salary |
|---|---|---|
| 井上 | 7 | 505000 |
| 佐藤 | 2 | 720000 |
| 中村 | 2 | 530000✕ |
| 伊藤 | 3 | 530000✕ |
| … | ||
自分の部署の平均より上か自部署の平均より上の行
| name | busho_id | salary |
|---|---|---|
| 井上 | 7 | 505000 |
| 佐藤 | 2 | 720000 |
| … | ||
WHERE salary > (SELECT avg(s2.salary) FROM shain s2 WHERE s2.busho_id = shain.busho_id)
- 取り消し線= 条件に合わないので結果に出てこない行
- 社員 18 行 → 自部署の平均より上の行 5 行(13 行が消えます)
18 行のうち残るのは 5 行です。同じ 530000 の中村と伊藤は消え、それより低い井上(505000)が残ります。給与の額ではなく、自分の部署の平均と比べているからです。
相関副問い合わせは、外側の行ごとに計算し直される副問い合わせです。 「自分の部署の平均」のように、行によって答えが変わる基準と比べたいときに使います。書き忘れてもエラーにならないのがいちばんの落とし穴です。
主要 7 製品で通用 そのまま使える: PostgreSQL / MySQL / MariaDB / SQLite / SQL Server / Oracle Database / IBM Db2
このページの実行結果と図は、すべて本物のデータベースに同じ SQL を流して得たものです。手で書いた「たぶんこうなる」ではありません。
01外側の行ごとに計算し直される
結論: 副問い合わせの中で外側の表の列を使うと、外側の 1 行ごとに内側が計算し直されます。 これを相関と呼びます。
ふつうの(相関していない)副問い合わせは、外側とは無関係に1 回だけ計算され、全行が同じ値と比べられます。相関すると、行ごとに比べる相手が変わります。
「自分の部署の平均より高い人」は、相関でしか書けません。部署が違えば比べる平均も違うからです。
書き方のポイントは 1 つだけです。
- 外側と内側に別々の別名を付ける(
shain sとshain s2のように) - 内側の条件で外側の別名を使う(
s2.busho_id = s.busho_id)
同じ表を 2 回使うので、別名が無いとどちらの列か区別できません。
SELECT s.name, s.busho_id, s.salary外側は社員 1 人ぶんの行FROM shain s外側の別名は sWHERE s.salary > (その人の給与と比べるSELECT avg(s2.salary) FROM shain s2内側の別名は s2WHERE s2.busho_id = s.busho_id)★その人と同じ部署だけを平均する
最後の行に s(外側)が出てくることが相関の目印です。ここに s が無ければ、内側は外側と無関係に 1 回計算されるだけです。
SELECT s.name, s.busho_id, s.salary
FROM shain s
WHERE s.salary > (SELECT avg(s2.salary)
FROM shain s2
WHERE s2.busho_id = s.busho_id)
ORDER BY s.shain_id;| name | busho_id | salary |
|---|---|---|
| 佐藤 | 2 | 720000 |
| 加藤 | 5 | 610000 |
| 山田 | 3 | 700000 |
| 井上 | 7 | 505000 |
| 清水 | 4 | 650000 |
(5 行)
5 人です。井上は 505000 とけっして高くありませんが、マーケの平均(451500)より上なので残っています。
02⚠️ よくある間違い:非相関との違いは件数ではなく顔ぶれに出る
結論: 相関を付け忘れても件数が同じになることがあります。 違いは誰が残るかに出ます。
このデータでは、「全体の平均より上」も「自部署の平均より上」もどちらも 5 人です。しかし顔ぶれは違います。
- 鈴木(98 万・経営)… 全体平均より上なので前者には残るが、経営は 1 人なので部署の平均=自分自身。
>は成り立たず後者では消える - 井上(50.5 万・マーケ)… 全体平均より下なので前者では消えるが、マーケの平均 45.15 万より上なので後者には残る
件数を数えても間違いに気づけません。 「5 件返ったから合っている」と判断すると、そのまま通ります。
確かめ方は、比べている基準を列として出すことです。 次の節のように平均を横に並べれば、どの基準と比べているのかが目で分かります。
SELECT s.name, s.busho_id, s.salary
FROM shain s
WHERE s.salary > (SELECT avg(salary) FROM shain)
ORDER BY s.shain_id;| name | busho_id | salary |
|---|---|---|
| 鈴木 | 1 | 980000 |
| 佐藤 | 2 | 720000 |
| 加藤 | 5 | 610000 |
| 山田 | 3 | 700000 |
| 清水 | 4 | 650000 |
(5 行)
同じ 5 人ですが、鈴木がいて井上がいません。上の例と見比べてください。
03SELECT 句に置くと基準が見える
結論: 相関副問い合わせを SELECT の列にすると、行ごとの基準がそのまま列になります。
さきほどの「確かめ方」がこれです。各行の横に「その人の部署の平均」が出るので、どの値と比べているのかが一目で分かります。
相関していない副問い合わせを列に置くと全行が同じ値になりますが、相関させると部署ごとに違う値が並びます。この違いが、相関しているかどうかの見分け方にもなります。
⚠️ 末尾の 2 行に注目してください。所属が未設定の社員は、同じ部署の人が 1 人もいないため平均が NULL になっています。次の節で扱います。
SELECT s.name,
s.busho_id,
s.salary,
(SELECT round(avg(s2.salary))
FROM shain s2
WHERE s2.busho_id = s.busho_id) AS busho_heikin
FROM shain s
ORDER BY s.shain_id;| name | busho_id | salary | busho_heikin |
|---|---|---|---|
| 鈴木 | 1 | 980000 | 980000 |
| 佐藤 | 2 | 720000 | 553763 |
| 田中 | 2 | 520000 | 553763 |
| 中村 | 2 | 530000 | 553763 |
| 小林 | 2 | 445050 | 553763 |
| 加藤 | 5 | 610000 | 539000 |
| 吉田 | 5 | 468000 | 539000 |
| 山田 | 3 | 700000 | 532750 |
| 高橋 | 3 | 480000 | 532750 |
| 伊藤 | 3 | 530000 | 532750 |
| 松本 | 3 | 421000 | 532750 |
| 井上 | 7 | 505000 | 451500 |
| 木村 | 7 | 398000 | 451500 |
| 清水 | 4 | 650000 | 539000 |
| 斎藤 | 4 | 455000 | 539000 |
| 山本 | 4 | 512000 | 539000 |
| 渡辺 | NULL | 400000 | NULL |
| 大野 | NULL | 432000 | NULL |
(18 行)
部署ごとに違う平均が並びます。経営の鈴木は 1 人しかいないので、平均が自分の給与と同じ 980000 です。
⚠️ この書き方は SQL Server / IBM Db2 では使えません(round(x)(引数 1 つ))。製品ごとの対応表
04⚠️ よくある間違い:相関を書き忘れてもエラーにならない
結論: 内側で外側の別名を使い忘れると、相関しないまま SQL として成立します。
よくあるのは WHERE s2.busho_id = s.busho_id と書くつもりで、両方とも s2 にしてしまう間違いです。s2.busho_id = s2.busho_id は自分自身との比較なので、NULL の行以外はすべて真になります。
つまり内側は「ほぼ全員の平均」を計算しており、部署ごとの比較になっていません。
エラーは出ません。 しかもこのデータではやはり 5 人が返り、顔ぶれも「全体の平均より上」と同じになります。前の節の話がそのまま当てはまり、件数でも顔ぶれでも気づけません。
目で確かめるしかない場所です。 内側の条件に外側の別名(この例では s)が出てくるかを、書いたら必ず見てください。
SELECT s.name, s.busho_id, s.salary
FROM shain s
WHERE s.salary > (SELECT avg(s2.salary)
FROM shain s2
WHERE s2.busho_id = s2.busho_id)
ORDER BY s.shain_id;| name | busho_id | salary |
|---|---|---|
| 鈴木 | 1 | 980000 |
| 佐藤 | 2 | 720000 |
| 加藤 | 5 | 610000 |
| 山田 | 3 | 700000 |
| 清水 | 4 | 650000 |
(5 行)
5 人返りますが、これは部署ごとの比較ではありません。内側に s がどこにも出てこないことが証拠です。
05⚠️ よくある間違い:相関の相手が無い行は落ちる
結論: 内側が 1 行も見つからないと平均は NULL になり、比較が unknown になってその行は消えます。
所属が未設定の社員は s2.busho_id = s.busho_id に一致する行が 1 つもありません。集約の対象が 0 行なので avg() は NULL を返し、salary > NULL は unknown になります。
そのため「自部署の平均より上」の結果には、所属未設定の 2 人が最初から入りません。除外したつもりが無いのに除外されている、という状態です。
count(*) なら 0 が返るのに avg() は NULL が返る、という違いも合わせて覚えておくと役に立ちます。
NULL の行も残したいなら、OR s.busho_id IS NULL のように明示的に足してください。 何も書かなければ「静かに落ちる」ほうが選ばれます。
SELECT s.name,
s.busho_id,
s.salary,
(SELECT count(*)
FROM shain s2
WHERE s2.busho_id = s.busho_id) AS onaji_busho_no_ninzu
FROM shain s
WHERE s.busho_id IS NULL
ORDER BY s.shain_id;| name | busho_id | salary | onaji_busho_no_ninzu |
|---|---|---|---|
| 渡辺 | NULL | 400000 | 0 |
| 大野 | NULL | 432000 | 0 |
(2 行)
2 人とも 0 です。count は 0 を返しますが、同じ条件で avg を取ると NULL になります。だから比較が成り立たず、行が消えます。
06まとまりごとの代表を取り出す
結論: 「部署ごとにいちばん給与が高い人」のような問いは、相関副問い合わせの定番です。
GROUP BY だけでは「部署ごとの最高額」は出せても、その額の人が誰かは出せません。集約すると行が潰れてしまうからです。相関なら、各行について「自分の部署の最高額」と自分を比べるだけで済みます。
同額の人が複数いれば全員返るのも、この書き方の性質です。
⚠️ 同じことは、集約結果を作ってから結合しても書けます。どちらが読みやすいかは場合によります。行数が多い表では実行計画も変わるので、遅いと感じたら両方試してください。
SELECT s.name, s.busho_id, s.salary
FROM shain s
WHERE s.salary = (SELECT max(s2.salary)
FROM shain s2
WHERE s2.busho_id = s.busho_id)
ORDER BY s.busho_id;| name | busho_id | salary |
|---|---|---|
| 鈴木 | 1 | 980000 |
| 佐藤 | 2 | 720000 |
| 山田 | 3 | 700000 |
| 清水 | 4 | 650000 |
| 加藤 | 5 | 610000 |
| 井上 | 7 | 505000 |
(6 行)
6 人です。社員が 1 人もいない法務は、そもそも社員表に行が無いので出てきません。
SELECT s.name, s.busho_id, s.salary
FROM shain s
JOIN (SELECT busho_id, max(salary) AS m
FROM shain
GROUP BY busho_id) t
ON t.busho_id = s.busho_id
AND t.m = s.salary
ORDER BY s.busho_id;| name | busho_id | salary |
|---|---|---|
| 鈴木 | 1 | 980000 |
| 佐藤 | 2 | 720000 |
| 山田 | 3 | 700000 |
| 清水 | 4 | 650000 |
| 加藤 | 5 | 610000 |
| 井上 | 7 | 505000 |
(6 行)
同じ 6 人です。先に「部署ごとの最高額」の表を作り、それと突き合わせています。相関副問い合わせより手順が見えやすい、と感じる人もいます。
自分で打ってみる
このページの例と同じデータが入った本物のデータベースを、この場(あなたのブラウザの中)で起動できます。打った SQL がサーバーへ送られることはありません。
押すと初回だけ約 9MB を読み込みます(2 回目以降はキャッシュされます)。
解いてみる
読んだだけでは書けるようになりません。実行結果で採点します。
関連するトピック
- スカラー副問い合わせ相関していない副問い合わせの基本です。
- IN / EXISTSEXISTS は相関して使うのがふつうです。
- FROM 句の副問い合わせ集約結果を表として使う書き方です。
- GROUP BY 句まとまりごとの集計そのものはこちらです。
根拠(一次情報)
- PostgreSQL 18 マニュアル: 副問い合わせ式一次情報・確認 2026-08-03
- Microsoft SQL Server: 相関副問い合わせ一次情報・確認 2026-08-03
- PostgreSQL 18 マニュアル: 集約関数(対象が 0 行のときの戻り値)一次情報・確認 2026-08-03