【完全ガイド】ORA-00936: missing expression の原因と解決方法|SELECT・FROM・CASE・動的 SQL 徹底解説

【完全ガイド】ORA-00936: missing expression の原因と解決方法|SELECT・FROM・CASE・動的 SQL 徹底解説

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 の後の条件漏れ
  • CASEEND 忘れ
  • 予約語の誤使用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 に冷静に対処できるようになります。


目次

結論: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-00933SQL 文が正しく終了しない
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 公式


まとめ

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)もあわせてご確認ください。