もうJOINでテーブルを結合するやり方は知ってるよね。でも実際の現場だとキーが一致するだけじゃ足りないことが多いんだ。たとえば、追加の条件を満たすデータだけ結合したいってことがよくある。例えば、アクティブなレコードだけとか、今年のデータだけとか、完了した注文だけとかね。
そこでONにANDを追加して拡張する出番!
JOIN ... ONの追加条件を使うと、どの行を結合に使うかを細かくコントロールできる。SQLが結果を作る前にフィルタできるから、クエリが:
- 速くなる(JOINを通る行が減る)、
- 正確になる(結合時点でフィルタリング)、
- LEFT JOINで予測しやすくなる(WHEREでのフィルタと違って)。
例:アクティブなコース登録だけ
たとえば、enrollmentsテーブルには学生の参加ステータスが入ってる:active、dropped、pending。
studentsテーブル:
| id | name |
|---|---|
| 1 | Otto Song |
| 2 | Maria Chi |
| 3 | Alex Lin |
enrollmentsテーブルを更新:
| student_id | course_id | status |
|---|---|---|
| 1 | 101 | active |
| 1 | 103 | active |
| 2 | 102 | dropped |
| 3 | 101 | active |
coursesテーブル:
| id | name |
|---|---|
| 101 | Mathematics |
| 102 | Physics |
| 103 | Computer Science |
今、アクティブなコースを持ってる学生だけを取得したい:
SELECT
students.name AS student_name,
courses.name AS course_name
FROM students
INNER JOIN enrollments
ON students.id = enrollments.student_id
AND enrollments.status = 'active'
INNER JOIN courses
ON enrollments.course_id = courses.id;
結果:
| student_name | course_name |
|---|---|
| Otto Song | Mathematics |
| Otto Song | Computer Science |
| Alex Lin | Mathematics |
ここではAND enrollments.status = 'active'をONの中に追加して、アクティブなレコードだけで結合するようにしてる。結合後にフィルタするんじゃなくてね。
なんでWHEREじゃダメ?
こう書くこともできる:
...
WHERE enrollments.status = 'active'
でもこれはLEFT JOINだと動きが違う。WHEREでフィルタすると、マッチしない行(NULL)が消えちゃって、LEFT JOINがINNER JOINみたいになっちゃう。
でもONの中にAND enrollments.status = 'active'を入れると、どの行が結合に入るかを直接コントロールできる。結果をフィルタするんじゃなくて、最初から結合対象を絞る感じ。
このやり方は、もう一方のテーブルに合う値がなくても、片方のテーブルの行を残したいとき(レポートや分析でよくある)に特に大事!
他のON ... AND ...の使い方例
studentsテーブル:
| id | name |
|---|---|
| 1 | Otto Song |
| 2 | Maria Chi |
| 3 | Alex Lin |
enrollmentsテーブル:
| student_id | course_id | status | enrolled_at |
|---|---|---|---|
| 1 | 101 | active | 2025-02-01 |
| 1 | 103 | active | 2025-03-05 |
| 2 | 102 | dropped | 2024-05-15 |
| 3 | 101 | active | 2025-03-12 |
coursesテーブル:
| id | name |
|---|---|
| 101 | Math |
| 102 | Physics |
| 103 | CS |
例:今年のコースだけ
SELECT
students.name,
courses.name,
enrollments.enrolled_at
FROM students
JOIN enrollments
ON students.id = enrollments.student_id
AND EXTRACT(YEAR FROM enrollments.enrolled_at) = EXTRACT(YEAR FROM CURRENT_DATE)
JOIN courses
ON enrollments.course_id = courses.id;
ここでは今年のデータだけ結合してる。
| name | name | enrolled_at |
|---|---|---|
| Otto Song | Math | 2025-02-01 |
| Otto Song | CS | 2025-03-05 |
| Alex Lin | Math | 2025-03-12 |
例:値で除外
JOIN enrollments
ON students.id = enrollments.student_id
AND enrollments.status != 'dropped'
退会した学生を結合時点で除外してる。後からフィルタするんじゃなくて。
| name | name |
|---|---|
| Otto Song | Math |
| Otto Song | CS |
| Alex Lin | Math |
条件をONの中に書くと、PostgreSQLは結合プランを最適化できて、処理する行が減る。大量データだと特に重要!JOIN後に「落とす」より、内部でフィルタした方が効率いいよ。
JOIN ONはキーだけじゃない
「ONはid = idだけ」って思ってる人多いけど、実は:
- 論理演算子:
AND、OR、NOT - 比較:
>、<、<>、BETWEEN、IN - 式:
EXTRACT、DATE_TRUNC、COALESCE、NULLIF
全部組み合わせてみよう
studentsテーブル:
| id | name |
|---|---|
| 1 | Otto Song |
| 2 | Maria Chi |
| 3 | Alex Lin |
facultiesテーブル:
| id | name | |
|---|---|---|
| 10 | Engineering | |
| 20 | Natural Sciences | |
| 30 | ← 名前なし (NULL) |
coursesテーブル:
| id | name | teacher | faculty_id |
|---|---|---|---|
| 101 | Math | Liam Park | 10 |
| 102 | Physics | Chloe Zhang | 20 |
| 103 | CS | Noah Kim | 10 |
| 104 | PE | Ava Chen | 30 |
enrollmentsテーブル:
| student_id | course_id | status |
|---|---|---|
| 1 | 101 | active |
| 1 | 103 | active |
| 2 | 102 | dropped |
| 3 | 101 | active |
| 3 | 104 | active |
SELECT
s.name AS student_name,
c.name AS course_name,
f.name AS faculty_name
FROM students s
JOIN enrollments e
ON s.id = e.student_id
AND e.status = 'active'
JOIN courses c
ON e.course_id = c.id
AND c.name != 'PE'
JOIN faculties f
ON c.faculty_id = f.id
AND f.name IS NOT NULL;
ここでは同時に:
- アクティブなレコード、
- 「PE」以外のコース、
- 名前がある学部
でフィルタしてる。
クエリの結果:
| student_name | course_name | faculty_name |
|---|---|---|
| Otto Song | Math | Engineering |
| Otto Song | CS | Engineering |
| Alex Lin | Math | Engineering |
このレクチャー、気に入ってくれたら嬉しい!JOINにフィルタを組み合わせて使うのは、これからめっちゃよく出てくるからね。ほぼ毎回使うよ :)
GO TO FULL VERSION