【完全ガイド】ORA-00936: missing expression の原因と解決方法|SELECT・FROM・CASE・動的 SQL 徹底解説
- 作成日 2026.08.04
- Oracle Database
Oracle 開発者が必ず一度は遭遇するSQL 構文エラー:
SELECT FROM employees;
-- ORA-00936: missing expression
SELECT first_name, last_name, FROM employees;
-- ORA-00936(末尾のカンマ)
SELECT * FROM employees WHERE id = ;
-- ORA-00936(値がない)
「式が欠けている」というシンプルなメッセージ。Oracle パーサーが文法的に不完全な SQL を拒否したことを意味します。一見単純ですが、実務では多岐にわたる原因があります:
SELECTの後の列指定漏れ(初心者頻出)FROM節漏れ(Oracle は DUAL でも必須)- 末尾のカンマ余分(
SELECT a, b,) AND/ORの後の条件漏れCASEのEND忘れ- 予約語の誤使用(
SELECT TABLE) DISTINCTの位置間違い**演算子(SQL では非対応)- 連結演算子
||の後のオペランド漏れ - 動的 SQL の連結ミス(バインド変数抜け)
- IN リストの空
- サブクエリの構文ミス
さらに、モダンなアプリ開発で特有の罠:
# Rails: 動的な WHERE の失敗
User.where("name = ?", nil)
# → 'SELECT * FROM users WHERE name = '
# → ORA-00936
# Rails: 空の配列
User.where(id: [])
# → 'SELECT * FROM users WHERE id IN ()'
# → ORA-00936 or ORA-00907
多くの日本語記事が「構文をチェックしろ」で終わりますが、実務では:
- エラー位置の特定(Oracle は位置情報を返す)
- 姉妹エラー(ORA-00933, ORA-00907, ORA-00904)との厳密な違い
||(連結) と Oracle 特有の罠- Rails / Java の動的 SQL でのバインド変数抜け
- 予約語の完全リスト
- 二分探索でのエラー箇所特定
- IDE / SQLcl でのオートフォーマット活用
- 正しい
SELECT ... FROM DUALの使い方
さらに、予期しないケース も存在します:
-- ❌ Oracle は ** 演算子非対応(PL/SQL のみ)
SELECT 2**8 FROM DUAL;
-- ORA-00936
-- ✅ 正解
SELECT POWER(2, 8) FROM DUAL;
これは Oracle と他 DB(PostgreSQL 等)の差異で、他 DB からの移行で頻発します。
本記事では、ORA-00936: missing expression の完全な原因と解決方法を、リファレンスとして実用的に整理します。10大発生パターン、姉妹エラー3種との厳密な違い、5つの解決策、Rails/Java/Python 対応、実践シナリオ、FAQまで完全網羅。この1本で ORA-00936 に冷静に対処できるようになります。
- 1. 結論:SQL 構文が不完全
- 2. Oracle パーサーの動作
- 3. 【原因①】SELECT の後の列指定漏れ
- 4. 【原因②】末尾のカンマ余分
- 5. 【原因③】FROM 節漏れ
- 6. 【原因④】WHERE / HAVING の条件漏れ
- 7. 【原因⑤】CASE の END 漏れ
- 8. 【原因⑥】予約語の誤使用
- 9. 【原因⑦】DISTINCT の位置間違い
- 10. 【原因⑧】** 演算子の誤用
- 11. 【原因⑨】連結演算子(||)の後のオペランド漏れ
- 12. 【原因⑩】動的 SQL の連結ミス
- 13. 姉妹エラーとの厳密な違い
- 14. Rails / Java / Python 対応
- 15. 実践シナリオ
- 16. トラブルシューティング
- 17. よくある質問(FAQ)
- 18. 参考リンク
- 19. まとめ
結論:SQL 構文が不完全
時間がない方向けに、最速の対処を先に示します。
エラーメッセージの読み方
ORA-00936: missing expression
↑
式が欠けている
→ SQL の文法的な不完全さ
エラー発生位置:
SELECT FROM employees
*
ERROR at line 1:
ORA-00936: missing expression
↑ ここで発生
アスタリスク(*)で示される位置を確認。
最速の診断
STEP 1: エラーの発生位置を確認(* マーク)
STEP 2: 前後の SQL を目視
STEP 3: よくあるパターンをチェック
- SELECT の後: 列指定?
- FROM の後: テーブル名?
- WHERE の後: 条件?
- AND/OR の後: 条件?
- カンマの後: 何か続いているか?
- CASE: END あるか?
STEP 4: 二分探索で問題箇所を絞る
5つの解決策
| # | 手法 | 使う場面 |
|---|---|---|
| ① | 構文の目視チェック | 単純なミス |
| ② | 二分探索 | 長い SQL |
| ③ | IDE のフォーマッター | 見落とし発見 |
| ④ | バインド変数チェック | 動的 SQL |
| ⑤ | 段階的組み立て | 予防 |
SQL 構文エラー3種の違い
| エラー | 意味 |
|---|---|
| ORA-00936 | 式が欠けている |
| ORA-00933 | SQL 文が正しく終了しない |
| ORA-00907 | 右カッコがない |
| ORA-00904 | 識別子が無効(列名等) |
| ORA-00942 | テーブル/ビューが存在しない |
SQL 構文エラーファミリー、位置と原因で区別。
詳細は以下で解説します。
Oracle パーサーの動作
左から右へトークン解析
Oracle パーサーは:
1. 左から右へトークン読み取り
2. 各時点で期待される次のトークンを判定
3. 期待に合わないと ORA-00936 等
「式」とは
式(expression):
- 列名:
first_name - リテラル:
123,'text',DATE '2026-06-15' - 関数呼び出し:
TO_CHAR(sysdate) - 演算式:
salary * 12 - サブクエリ:
(SELECT ...) - CASE 式:
CASE ... END
これらの「式」が期待される位置に何もない → ORA-00936。
発生位置
ORA-00936 は「式が期待される位置で式が見つからない」
つまり、SQL パーサーが「式」を待っているのに
別のトークンが来た or 何も来なかった状態
【原因①】SELECT の後の列指定漏れ
症状
-- ❌ 列指定なし
SELECT FROM employees;
-- ORA-00936: missing expression
-- ❌ 全て空欄
SELECT ,,, FROM employees;
解決
-- ✅ 列指定
SELECT first_name, last_name FROM employees;
-- ✅ 全列
SELECT * FROM employees;
初心者頻出、ORA-00936 の典型。
【原因②】末尾のカンマ余分
症状
-- ❌ カンマの後に列なし
SELECT first_name, last_name, FROM employees;
-- ↑ 余分
解決
-- ✅ カンマを削除
SELECT first_name, last_name FROM employees;
同様のパターン:
-- INSERT VALUES
INSERT INTO t VALUES (1, 2, ); -- 余分
INSERT INTO t VALUES (1, 2); -- 正解
-- ORDER BY
ORDER BY a, b, -- 余分
ORDER BY a, b -- 正解
【原因③】FROM 節漏れ
症状
-- ❌ FROM なし
SELECT first_name, last_name;
-- ORA-00923 or ORA-00936(バージョンにより)
解決
-- ✅ FROM 必須
SELECT first_name, last_name FROM employees;
-- テーブル参照なしなら DUAL
SELECT SYSDATE FROM DUAL;
Oracle 23ai の新機能
-- Oracle 23ai: SELECT without FROM
SELECT SYSDATE;
-- OK(DUAL 不要)
23ai の待望の機能(PostgreSQL / MySQL 等では標準)。
【原因④】WHERE / HAVING の条件漏れ
症状
-- ❌ WHERE の後に何もない
SELECT * FROM employees WHERE ;
-- ❌ AND/OR の後に条件なし
SELECT * FROM employees WHERE id = 1 AND ;
-- ❌ 演算子の後に値なし
SELECT * FROM employees WHERE id = ;
解決
-- ✅ 条件を追加
SELECT * FROM employees WHERE id = 100;
SELECT * FROM employees WHERE id = 1 AND status = 'A';
【原因⑤】CASE の END 漏れ
症状
-- ❌ END なし
SELECT CASE
WHEN status = 'A' THEN 'active'
ELSE 'inactive'
-- END 忘れ
FROM employees;
-- ORA-00936
-- ❌ WHEN の後の条件漏れ
SELECT CASE
WHEN THEN 'active'
END
FROM employees;
解決
SELECT CASE
WHEN status = 'A' THEN 'active'
ELSE 'inactive'
END AS status_label
FROM employees;
【原因⑥】予約語の誤使用
症状
-- ❌ TABLE は予約語
SELECT TABLE FROM employees;
-- ORA-00936
-- ❌ 予約語を列名に
SELECT LEVEL FROM employees;
-- LEVEL は疑似列扱い、意図と異なる
解決
A. 引用符で強制的に列名:
SELECT "TABLE" FROM employees;
B. 別名:
ALTER TABLE employees RENAME COLUMN "TABLE" TO table_name;
SELECT table_name FROM employees;
予約語の詳細は ORA-00904: invalid identifier の記事も参照してください。
【原因⑦】DISTINCT の位置間違い
症状
-- ❌ DISTINCT が途中
SELECT a, b, DISTINCT c FROM t;
-- ORA-00936(DISTINCT は SELECT 直後のみ)
解決
-- ✅ SELECT 直後
SELECT DISTINCT a, b, c FROM t;
【原因⑧】** 演算子の誤用
症状(PostgreSQL 経験者ハマる)
-- ❌ Oracle SQL では ** 不可
SELECT 2**8 FROM DUAL;
-- ORA-00936
-- ✅ ただし PL/SQL 内では OK
BEGIN
DBMS_OUTPUT.PUT_LINE(2**8); -- OK
END;
/
解決
POWER 関数:
SELECT POWER(2, 8) FROM DUAL;
-- 256
または EXP + LN:
SELECT EXP(8 * LN(2)) FROM DUAL;
【原因⑨】連結演算子(||)の後のオペランド漏れ
症状
-- ❌ || の後に何もない
SELECT dept_name || ' ' || FROM dept;
-- ↑
-- ORA-00936
解決
SELECT dept_name || ' ' || dept_code FROM dept;
【原因⑩】動的 SQL の連結ミス
症状(Rails / Java 頻出)
# ❌ Rails
name = nil
User.where("name = ?", name)
# → SQL: SELECT * FROM users WHERE name =
# → ORA-00936
# ❌ 空の配列
User.where(id: [])
# → SELECT * FROM users WHERE id IN ()
# → ORA-00936
// ❌ Java 文字列連結
String sql = "SELECT * FROM users WHERE id = " + userId;
// userId が null → "WHERE id = "
解決
Rails:
# ✅ NULL チェック
if name.present?
User.where("name = ?", name)
else
User.all
end
# ✅ 空配列チェック
User.where(id: ids) if ids.any?
Java:
// ✅ PreparedStatement
PreparedStatement ps = conn.prepareStatement(
"SELECT * FROM users WHERE id = ?"
);
ps.setInt(1, userId);
Rails 8 系の詳細は Rails 8 アップグレードガイドの記事、find/find_by/where の記事も参照してください。
姉妹エラーとの厳密な違い
ORA-00933: SQL command not properly ended
-- SQL が続きすぎる
SELECT * FROM t WHERE id = 1 EXTRA_STUFF;
-- ORA-00933
ORA-00907: missing right parenthesis
-- カッコが閉じていない
SELECT * FROM t WHERE id IN (1, 2;
-- ORA-00907
ORA-00904: invalid identifier
-- 列名が存在しない
SELECT nonexistent_col FROM t;
-- ORA-00904
ORA-00942: table or view does not exist
-- テーブルが存在しない
SELECT * FROM nonexistent_table;
-- ORA-00942
使い分け表
| エラー | 状況 |
|---|---|
| ORA-00936 | 式が足りない |
| ORA-00933 | 余分な文字がある |
| ORA-00907 | ) がない |
| ORA-00904 | 列名が違う |
| ORA-00942 | テーブルがない |
Rails / Java / Python 対応
Rails ActiveRecord
動的クエリの防御:
class SearchService
def self.search(params)
users = User.all
users = users.where("name LIKE ?", "%#{params[:name]}%") if params[:name].present?
users = users.where(status: params[:status]) if params[:status].present?
users
end
end
空配列対応:
def find_by_ids(ids)
return User.none if ids.blank?
User.where(id: ids)
end
エラーハンドリング:
begin
users = User.where(sql_condition).to_a
rescue ActiveRecord::StatementInvalid => e
if e.message.include?("ORA-00936")
Rails.logger.error "SQL 構文エラー: #{sql_condition}"
raise ArgumentError, "無効なクエリ"
end
raise
end
Rails 8 系の詳細は Rails 8 アップグレードガイドの記事、find/find_by/where の記事、Solid Queue 使い方の記事も参照してください。
Java (JDBC)
// ❌ 文字列連結(危険)
String sql = "SELECT * FROM users WHERE id = " + userId;
// ✅ PreparedStatement
try (PreparedStatement ps = conn.prepareStatement(
"SELECT * FROM users WHERE id = ?"
)) {
ps.setInt(1, userId);
ResultSet rs = ps.executeQuery();
}
// ✅ 動的な WHERE
StringBuilder sb = new StringBuilder("SELECT * FROM users WHERE 1=1");
List<Object> params = new ArrayList<>();
if (name != null) {
sb.append(" AND name = ?");
params.add(name);
}
if (status != null) {
sb.append(" AND status = ?");
params.add(status);
}
Python (oracledb)
import oracledb
# ❌ 文字列連結
sql = f"SELECT * FROM users WHERE id = {user_id}"
# ✅ バインド変数
cursor.execute(
"SELECT * FROM users WHERE id = :1",
(user_id,)
)
# ✅ 動的 WHERE
conditions = []
params = {}
if name:
conditions.append("name = :name")
params['name'] = name
if status:
conditions.append("status = :status")
params['status'] = status
where_clause = " AND ".join(conditions) if conditions else "1=1"
sql = f"SELECT * FROM users WHERE {where_clause}"
cursor.execute(sql, params)
実践シナリオ
シナリオ1:長い SQL の二分探索デバッグ
-- 100行の SQL でエラー → 場所特定
-- STEP 1: 前半だけ実行
SELECT
a, b, c, d, e, f, g, h, i, j,
k, l, m, n, o
FROM my_view;
-- OK
-- STEP 2: 後半だけ実行
SELECT
p, q, r, s, t, u, v, w, x, y, z
FROM my_view;
-- ORA-00936
-- STEP 3: さらに絞り込み
SELECT p, q, r FROM my_view; -- OK
SELECT s, t, u FROM my_view; -- OK
SELECT v, w, x FROM my_view; -- ORA-00936(発見!)
-- STEP 4: 個別チェック
SELECT v FROM my_view; -- ORA-00936(原因)
シナリオ2:Rails 動的クエリの安全設計
class UserSearchForm
attr_accessor :name, :email, :status, :ids
def search
scope = User.all
scope = scope.where("LOWER(name) LIKE ?", "%#{name.downcase}%") if name.present?
scope = scope.where("email = ?", email) if email.present?
scope = scope.where(status: status) if status.present?
scope = scope.where(id: ids) if ids.present? && ids.any?
scope
end
end
シナリオ3:SQLcl / SQL Developer での構文チェック
# SQLcl のフォーマット機能
$ sql user/pw
SQL> FORMAT
-- SQL 整形、エラー箇所が視覚的に分かりやすい
シナリオ4:CI/CD での SQL 検証
# .github/workflows/sql-check.yml
- name: Validate SQL files
run: |
for sql in db/migrate/*.sql; do
sqlplus -s $DB_USER/$DB_PASSWORD@$DB_DSN <<EOF
SET DEFINE OFF
SET ECHO ON
SET FEEDBACK ON
WHENEVER SQLERROR EXIT SQL.SQLCODE
@$sql
EOF
done
Kamal 2 デプロイの詳細は Kamal 2 デプロイの記事を参照してください。
シナリオ5:MERGE 文の構文チェック
-- 複雑な MERGE
MERGE INTO dst d
USING src s ON (d.id = s.id)
WHEN MATCHED THEN
UPDATE SET d.name = s.name
WHERE d.status = 'A'
WHEN NOT MATCHED THEN
INSERT (d.id, d.name)
VALUES (s.id, s.name);
各節(USING, ON, WHEN, UPDATE, INSERT)が完全か確認。
MERGE の詳細は Oracle MERGE 文 使い方の記事も参照してください。
シナリオ6:動的 SQL のログ
DECLARE
v_sql VARCHAR2(4000);
BEGIN
v_sql := 'SELECT * FROM employees WHERE dept = ' || l_dept;
DBMS_OUTPUT.PUT_LINE('SQL: ' || v_sql); -- 生成 SQL 確認
EXECUTE IMMEDIATE v_sql;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Error: ' || SQLERRM);
DBMS_OUTPUT.PUT_LINE('SQL was: ' || v_sql);
END;
/
シナリオ7:Python での動的 WHERE
def build_query(filters: dict) -> tuple[str, dict]:
conditions = []
params = {}
if filters.get('name'):
conditions.append("name = :name")
params['name'] = filters['name']
if filters.get('status'):
conditions.append("status = :status")
params['status'] = filters['status']
if not conditions:
return "SELECT * FROM users", {}
where_clause = " AND ".join(conditions)
return f"SELECT * FROM users WHERE {where_clause}", params
シナリオ8:Docker Oracle での再現
docker exec -it oracle-xe sqlplus scott/tiger <<EOF
SELECT , FROM DUAL;
-- ORA-00936
SELECT DUMMY FROM DUAL;
-- OK
EOF
Docker 関連は docker daemon 接続エラーの記事、Docker no space left on device の記事も参照してください。
シナリオ9:IDE でのフォーマット
SQL Developer:
- Ctrl + F7: フォーマット
- 括弧マッチング表示
- シンタックスハイライト
DataGrip / SQLcl:
- 構文チェック機能
- オートコンプリート
シナリオ10:Autonomous DB での構文チェック
-- Autonomous DB では
-- SQL Developer Web 内蔵
-- 高機能なフォーマッター
トラブルシューティング
エラー位置が分からない
SELECT ... FROM ... WHERE ...
*
ERROR at line 1:
ORA-00936: missing expression
-- * のマークが発生位置
動的 SQL でエラー
生成された SQL を出力:
DBMS_OUTPUT.PUT_LINE(v_sql);
エラーだが SQL は正しく見える
見えない文字の可能性:
-- 非印字文字(改行、タブ、Unicode 空白)
SELECT DUMP(sql_text) FROM v$sql WHERE ...;
Rails ログで確認
# log/development.log
config.log_level = :debug
# SQL がログに出力される
PostgreSQL からの移行
PG では動く
- SELECT 1+1;
- SELECT ** 演算子
- FROM 節不要
Oracle では
- SELECT 1+1 FROM DUAL; (23ai 以前)
- POWER(2, 8) 使用
- FROM 必須(23ai で改善)
JOIN 構文の混在
-- ❌ 古い (+) と ANSI 混在
SELECT * FROM a, b LEFT JOIN c ON b.id = c.id
WHERE a.id = b.a_id(+);
-- ORA-00936 の可能性
ANSI JOIN に統一:
SELECT * FROM a
LEFT JOIN b ON a.id = b.a_id
LEFT JOIN c ON b.id = c.id;
よくある質問(FAQ)
Q1. ORA-00936 と ORA-00933 の違い
- 00936: 式が足りない
- 00933: SQL が正しく終わっていない(余分な文字)
Q2. どのバージョンから発生
全バージョン。SQL 構文チェックの基本。
Q3. Oracle 23ai で改善は?
SELECT without FROM で軽減:
SELECT SYSDATE; -- 23ai OK
Q4. 動的 SQL でハマった
生成 SQL を出力して目視。
Q5. Rails での対処
present? チェック、if condition.any? 等。
Q6. Java での対処
PreparedStatement 使用、文字列連結避ける。
Q7. Python での対処
バインド変数、条件生成の分離。
Q8. IDE 選び
- SQL Developer: Oracle 純正、無料
- DataGrip: 商用、高機能
- SQLcl: モダンな CLI
Q9. エラー位置の見方
* マーク直前を確認。
Q10. パフォーマンスへの影響
構文エラーは実行前に検出、パフォーマンス影響なし。
Q11. Autonomous DB での挙動
同じ。SQL Developer Web で対処。
Q12. E-Business Suite での頻発
カスタムレポートの動的 SQL でよく発生。テンプレート要注意。
参考リンク
Oracle 公式
- Oracle Database Error Messages: ORA-00936
- Oracle Database SQL Language Reference: SELECT
- Oracle Database SQL Language Reference: Expressions
- Oracle Database 23ai: SELECT without FROM
まとめ
ORA-00936: missing expression の要点を再整理します。
エラーの本質
Oracle パーサーが「式」を期待する位置で
式が見つからなかった
→ SQL の文法的な不完全さ
エラーメッセージの読み方
SELECT FROM employees
*
ERROR at line 1:
ORA-00936: missing expression
↑ 発生位置
SQL 構文エラー5兄弟
| エラー | 意味 |
|---|---|
| ORA-00936 | 式が欠けている |
| ORA-00933 | 文が正しく終わらない |
| ORA-00907 | ) がない |
| ORA-00904 | 識別子無効 |
| ORA-00942 | テーブル/ビュー不在 |
10大原因
| # | 原因 | 対処 |
|---|---|---|
| ① | SELECT 列漏れ | 列指定 |
| ② | 末尾カンマ | 削除 |
| ③ | FROM 漏れ | 追加 or DUAL / 23ai |
| ④ | WHERE 条件漏れ | 条件追加 |
| ⑤ | CASE END 漏れ | END 追加 |
| ⑥ | 予約語誤用 | 引用符 or 別名 |
| ⑦ | DISTINCT 位置 | SELECT 直後 |
| ⑧ | ** 演算子 | POWER 関数 |
| ⑨ | ` | |
| ⑩ | 動的 SQL | バインド変数 |
5つの解決策
① 構文の目視チェック
② 二分探索で問題箇所絞る
③ IDE のフォーマッター活用
④ バインド変数チェック
⑤ 段階的組み立て
動的 SQL の防御
# Rails: nil チェック
scope = User.all
scope = scope.where(name: name) if name.present?
# Rails: 空配列
return User.none if ids.blank?
// Java: PreparedStatement
PreparedStatement ps = conn.prepareStatement(
"SELECT * FROM users WHERE id = ?"
);
ps.setInt(1, userId);
# Python: バインド変数
cursor.execute(
"SELECT * FROM users WHERE id = :1",
(user_id,)
)
Oracle 23ai の改善
-- SELECT without FROM
SELECT SYSDATE; -- 23ai OK
-- 以前: SELECT SYSDATE FROM DUAL;
予防のポイント
1. IDE のシンタックスハイライト活用
2. 動的 SQL は生成後にログ出力
3. バインド変数使用(文字列連結避ける)
4. 段階的組み立て(少しずつ追加)
5. Rails: present? / any? チェック
6. Java: PreparedStatement
7. Python: バインド変数
8. CI/CD で SQL 構文検証
9. コードレビューで SQL 確認
10. ORM 使用時も生成 SQL を確認
これらの知識は、Oracle での SQL 開発・デバッグ・レガシー移行・Rails / Java / Python アプリ開発・動的 SQL 設計・CI/CD パイプライン・IDE 活用など、あらゆる場面で活用できます。本記事をブックマークしておけば、ORA-00936 に出会っても冷静に的確に対処できるようになります。
本記事は2026年6月時点の情報をもとに、Oracle Database 19c〜23ai での動作確認・公式ドキュメントに基づき作成しています。Oracle のバージョンにより挙動が異なる場合があるため、最新の情報は Oracle 公式ドキュメント(docs.oracle.com)もあわせてご確認ください。
-
前の記事
【完全ガイド】ORA-00932: inconsistent datatypes の原因と解決方法|UNION・LOB・CASE・型変換 徹底解説 2026.08.04
-
次の記事
【完全ガイド】ORA-00933: SQL command not properly ended の原因と解決方法|UPDATE JOIN・INSERT ORDER BY・MySQL 移行 徹底解説 2026.08.05
コメントを書く