Oracle の JOIN の書き方 — 旧記法 (+) と ANSI 構文の対応表

古い Oracle のコードを読んでいると、FROM にテーブルをカンマで並べて WHERE で結合したり、WHERE a.id = b.id(+) のように (+) が付いていたりする SQL に出会います。これは Oracle が ANSI の JOIN 構文に対応する前から使われてきた旧記法で、(+) は Oracle 独自の外部結合演算子です。

結論から言うと、カンマ結合+WHERE は INNER JOIN、(+) は LEFT JOIN / RIGHT JOIN に対応します。この記事では実際の Oracle Database で両者の結果が一致することを確認しながら、書き換えの対応表と、書き換え時に踏みやすい Oracle 固有の癖をまとめます。なお、Oracle 公式マニュアルも新しく書くなら ANSI 構文を推奨しています(後述)。

実測環境は Docker の gvenzl/oracle-free:23-slim-faststart(バナー表記は Oracle AI Database 26ai Free Release 23.26.2.0.0)です。比較用の PostgreSQL 17.10 / MySQL 8.4.10 も Docker で動かしています。サンプルは次の2表です。

CREATE TABLE emp (id INT, name VARCHAR(20), dept_id INT);
CREATE TABLE dept (dept_id INT, dept_name VARCHAR(20));
INSERT INTO emp VALUES (1,'佐藤',10),(2,'鈴木',20),(3,'高橋',NULL),(4,'田中',40);
INSERT INTO dept VALUES (10,'営業部'),(20,'開発部'),(30,'総務部');

高橋は dept_id が NULL、田中の dept_id 40 は dept 側に存在せず、総務部(30)は emp 側に誰もいない、という配置です。なお、複数行をまとめて書く VALUES (…),(…) 形式の INSERT を Oracle が受け付けるのは 23ai 以降です。それより古い Oracle で手元に再現する場合は、INSERT 文を1行ずつに分けて実行してください。

対応表: 旧記法と ANSI 構文

旧 Oracle 記法ANSI 構文意味
FROM emp, dept WHERE emp.dept_id = dept.dept_idFROM emp INNER JOIN dept ON emp.dept_id = dept.dept_id内部結合
FROM emp, dept WHERE emp.dept_id = dept.dept_id(+)FROM emp LEFT JOIN dept ON emp.dept_id = dept.dept_id左外部結合
FROM emp, dept WHERE emp.dept_id(+) = dept.dept_idFROM emp RIGHT JOIN dept ON emp.dept_id = dept.dept_id右外部結合
(書けない → 後述)FROM emp FULL OUTER JOIN dept ON emp.dept_id = dept.dept_id完全外部結合

(+) は「こちら側に一致する行がなくても NULL で埋めて残す」側、つまり NULL でパディングされる側の列に付けます。dept 側に付ければ「emp は全行残す」= LEFT JOIN です。

カンマ結合 ⇔ INNER JOIN の結果一致

SELECT * FROM emp, dept WHERE emp.dept_id = dept.dept_id;

結果は次の2行で、INNER JOIN … ON に書き換えても同じ2行が返ります(実測)。

IDNAMEDEPT_IDDEPT_IDDEPT_NAME
1佐藤1010営業部
2鈴木2020開発部

SELECT * だと DEPT_ID が2回現れる点はカンマ結合も ON も同じです(USING を使うと1回にマージされます)。

(+) ⇔ LEFT JOIN の結果一致

SELECT * FROM emp, dept WHERE emp.dept_id = dept.dept_id(+);

dept 側の列に (+) を付けたので emp は全行残り、結果は次の4行です。

IDNAMEDEPT_IDDEPT_IDDEPT_NAME
1佐藤1010営業部
2鈴木2020開発部
3高橋(NULL)(NULL)(NULL)
4田中40(NULL)(NULL)
SELECT * FROM emp LEFT JOIN dept ON emp.dept_id = dept.dept_id;

こちらも同一の4行で、両者の結果が一致することを実測で確認しています。部署が引けなかった高橋(dept_id が NULL)と田中(dept_id 40 が dept に無い)が、dept 側 NULL のまま残るのが外部結合です。

(+) では書けないもの: FULL OUTER JOIN

「両方の表の行を全部残したい」からと (+) を両側に付けると、エラーになります(実測)。

SELECT * FROM emp, dept WHERE emp.dept_id(+) = dept.dept_id(+);
-- ORA-01468: a predicate may reference only one outer-joined table

(+) はひとつの条件でどちらか片側にしか付けられません。完全外部結合が必要なら ANSI 構文で書きます。

SELECT * FROM emp FULL OUTER JOIN dept ON emp.dept_id = dept.dept_id;

これは LEFT JOIN の4行に、emp 側に誰もいない総務部(ID・NAME が NULL)を加えた5行を返します(実測)。

公式マニュアルには他にも (+) の制限が列挙されています。主なものだけ挙げると、同じ問い合わせブロックで ANSI JOIN 構文と混在できない、(+) 付きの条件を OR で他の条件とつなげない、IN 条件に使えない、自己外部結合に使えない、などです。旧記法は「書けそうで書けない」パターンが多く、これが ANSI 構文を推奨する理由にもなっています。

ANSI 構文に書き換えるときの Oracle の癖

書き換え先の ANSI 構文側にも、Oracle 固有の引っかかりが2つあります。

ひとつ目は USING です。USING(dept_id) で結合した場合、その列に表修飾子(emp. など)を付けるとエラーになります(実測)。

SELECT emp.dept_id FROM emp JOIN dept USING (dept_id);
-- ORA-25154: column part of USING clause cannot have qualifier

USING の列は修飾なしの dept_id と書く必要があります。同じクエリが PostgreSQL 17 / MySQL 8.4 ではエラーにならず通る(どちらも 10, 20 の2行)ことも確認したので、他の DB から来た人ほど踏みやすい癖です。

ふたつ目は CROSS JOIN です。直積を取る CROSS JOIN に結合条件の ON を付けると構文エラーになります(実測)。

SELECT * FROM emp CROSS JOIN dept ON emp.dept_id = dept.dept_id;
-- ORA-03048: SQL reserved word 'ON' is not syntactically valid following ...

これは PostgreSQL と同じ挙動で、逆に MySQL や SQLite は通してしまいます(この違いは SQL の JOIN の種類と違いまとめ — INNER/LEFT/RIGHT/FULL/CROSS【2026年更新】 で扱っています)。条件を付けたいなら素直に INNER JOIN … ON と書きます。

なお JOIN = INNER JOIN のキーワード省略は Oracle でも実測で結果が同一になることを確認しています(LEFT OUTER JOINOUTER 省略なども、Oracle が ANSI 構文にフル対応している以上同様に有効です)。どこまで省略するかの整理は SQL の JOIN は省略できる — INNER/OUTER キーワードとカンマ結合の整理 にまとめました。

新しく書くなら ANSI 構文

Oracle の SQL Language Reference(Joins 節)は、外部結合について次のように明言しています。

Oracle recommends that you use the FROM clause OUTER JOIN syntax rather than the Oracle join operator.

つまり (+) ではなく FROM 句の OUTER JOIN 構文(ANSI 構文)を使うこと、という公式の推奨です。前述のとおり (+) には FULL OUTER が書けない・OR や IN と組み合わせられないなどの制限が多く、Oracle 以外の DB では通用しない方言でもあります。既存コードを読むための知識としては必要ですが、新規に書く SQL で選ぶ理由はありません。

さいごに

(+) は「付けた側が NULL でパディングされる側」と覚えれば、旧コードの読み替えはこの記事の対応表どおりです。書き換えの際は、FULL OUTER 相当は (+) では書けないこと(ORA-01468)、USING 列に修飾子を付けられないこと(ORA-25154)、CROSS JOINON を付けられないこと(ORA-03048)の3点に気をつけてください。

あわせて読みたい: