コマンド道場

相関副問い合わせ(外側の行ごとに計算する)の使い方

行ごとに違う基準と比べる

shain社員

namebusho_idsalary
井上7505000
佐藤2720000
中村2530000
伊藤3530000

自分の部署の平均より上か自部署の平均より上の行

namebusho_idsalary
井上7505000
佐藤2720000

WHERE salary > (SELECT avg(s2.salary) FROM shain s2 WHERE s2.busho_id = shain.busho_id)

  • 取り消し線= 条件に合わないので結果に出てこない行
  • 社員 18 行 → 自部署の平均より上の行 513 行が消えます)

18 行のうち残るのは 5 行です。同じ 530000 の中村と伊藤は消え、それより低い井上(505000)が残ります。給与の額ではなく、自分の部署の平均と比べているからです。

相関副問い合わせは、外側の行ごとに計算し直される副問い合わせです。 「自分の部署の平均」のように、行によって答えが変わる基準と比べたいときに使います。書き忘れてもエラーにならないのがいちばんの落とし穴です。

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

製品ごとの対応表(根拠つき)を見る

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

01外側の行ごとに計算し直される

結論: 副問い合わせの中で外側の表の列を使うと、外側の 1 行ごとに内側が計算し直されます。 これを相関と呼びます。

ふつうの(相関していない)副問い合わせは、外側とは無関係に1 回だけ計算され、全行が同じ値と比べられます。相関すると、行ごとに比べる相手が変わります

「自分の部署の平均より高い人」は、相関でしか書けません。部署が違えば比べる平均も違うからです。

書き方のポイントは 1 つだけです。

  • 外側と内側に別々の別名を付けるshain sshain s2 のように)
  • 内側の条件で外側の別名を使うs2.busho_id = s.busho_id

同じ表を 2 回使うので、別名が無いとどちらの列か区別できません。

1 行ずつ、何をしているか
  1. SELECT s.name, s.busho_id, s.salary外側は社員 1 人ぶんの行
  2. FROM shain s外側の別名は s
  3. WHERE s.salary > (その人の給与と比べる
  4. SELECT avg(s2.salary) FROM shain s2内側の別名は s2
  5. WHERE 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;
namebusho_idsalary
佐藤2720000
加藤5610000
山田3700000
井上7505000
清水4650000

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;
namebusho_idsalary
鈴木1980000
佐藤2720000
加藤5610000
山田3700000
清水4650000

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;
namebusho_idsalarybusho_heikin
鈴木1980000980000
佐藤2720000553763
田中2520000553763
中村2530000553763
小林2445050553763
加藤5610000539000
吉田5468000539000
山田3700000532750
高橋3480000532750
伊藤3530000532750
松本3421000532750
井上7505000451500
木村7398000451500
清水4650000539000
斎藤4455000539000
山本4512000539000
渡辺NULL400000NULL
大野NULL432000NULL

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)が出てくるかを、書いたら必ず見てください。

s2 と s2 を比べてしまった例
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;
namebusho_idsalary
鈴木1980000
佐藤2720000
加藤5610000
山田3700000
清水4650000

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 のように明示的に足してください。 何も書かなければ「静かに落ちる」ほうが選ばれます。

所属未設定の人は「同じ部署の人」が 0 人
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;
namebusho_idsalaryonaji_busho_no_ninzu
渡辺NULL4000000
大野NULL4320000

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;
namebusho_idsalary
鈴木1980000
佐藤2720000
山田3700000
清水4650000
加藤5610000
井上7505000

6

6 人です。社員が 1 人もいない法務は、そもそも社員表に行が無いので出てきません。

集約してから結合する書き方(結果は同じ 6 人)
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;
namebusho_idsalary
鈴木1980000
佐藤2720000
山田3700000
清水4650000
加藤5610000
井上7505000

6

同じ 6 人です。先に「部署ごとの最高額」の表を作り、それと突き合わせています。相関副問い合わせより手順が見えやすい、と感じる人もいます。

自分で打ってみる

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

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

解いてみる

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

関連するトピック

根拠(一次情報)