古い 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_id | FROM 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_id | FROM 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行が返ります(実測)。
| ID | NAME | DEPT_ID | DEPT_ID | DEPT_NAME |
|---|---|---|---|---|
| 1 | 佐藤 | 10 | 10 | 営業部 |
| 2 | 鈴木 | 20 | 20 | 開発部 |
SELECT * だと DEPT_ID が2回現れる点はカンマ結合も ON も同じです(USING を使うと1回にマージされます)。
(+) ⇔ LEFT JOIN の結果一致
SELECT * FROM emp, dept WHERE emp.dept_id = dept.dept_id(+);
dept 側の列に (+) を付けたので emp は全行残り、結果は次の4行です。
| ID | NAME | DEPT_ID | DEPT_ID | DEPT_NAME |
|---|---|---|---|---|
| 1 | 佐藤 | 10 | 10 | 営業部 |
| 2 | 鈴木 | 20 | 20 | 開発部 |
| 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 JOIN の OUTER 省略なども、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 JOIN に ON を付けられないこと(ORA-03048)の3点に気をつけてください。
あわせて読みたい: