【完全ガイド】ORA-00947: not enough values の原因と解決方法|INSERT・サブクエリ・CTAS の罠 徹底解説

【完全ガイド】ORA-00947: not enough values の原因と解決方法|INSERT・サブクエリ・CTAS の罠 徹底解説

Oracle 開発者が日常的に遭遇するINSERT エラー:

-- ❌ 列数と値の数が不一致
INSERT INTO employees(id, name, salary, dept_id) 
VALUES (100, 'John', 50000);
-- ORA-00947: not enough values

「値が足りない」というシンプルなメッセージ。しかし、実務では多岐にわたる原因があります:

  • INSERT VALUES で値の数が列数より少ない
  • 列リスト省略 + テーブル構造との不一致
  • INSERT ... SELECT でサブクエリの列数不足
  • WHERE (A, B) IN (SELECT C FROM ...)タプル比較
  • HAVING (A, B) IN ... の同様パターン
  • CTAS 後の列追加でずれる
  • OBJECT 型のコンストラクタ引数不足
  • MERGEINSERT部分
  • 動的 SQLの生成ミス
  • Rails マイグレーションでカラム追加後

このエラーの厄介さは、列リストを省略した INSERTで頻発する点:

-- ❌ 列リスト省略の罠
CREATE TABLE t (a NUMBER, b NUMBER, c NUMBER);

INSERT INTO t VALUES (1, 2);
-- ORA-00947(3列に2値)

-- 後で ALTER TABLE で列追加
ALTER TABLE t ADD (d NUMBER);
INSERT INTO t VALUES (1, 2, 3);
-- ORA-00947(4列に3値、既存 INSERT が壊れる)

**「一度動いていたコードが動かなくなる」**典型パターンです。

多くの日本語記事が「値を追加しろ」で終わりますが、実務では:

  • 姉妹エラー3種ORA-00913 = too many values, ORA-01400 = NULL not allowed)の厳密な区別
  • INSERT の列リスト明示による保守性向上
  • WHERE (A, B) IN (SELECT ...) のタプル比較の落とし穴
  • OBJECT 型 / User-Defined Type のコンストラクタ問題
  • CTAS 後のカラム追加時の対処
  • Rails / Java / Python からの防御的コーディング
  • CI/CD パイプラインでの事前検出

さらに、多くの開発者が知らないタプル比較の失敗パターン:

-- ❌ 2要素タプルで比較、サブクエリは1列のみ
SELECT * FROM employees 
WHERE (dept_id, salary) IN (SELECT dept_id FROM departments);
-- ORA-00947(サブクエリが 1 列のみ、タプルは 2 要素)

これは日本語で本質的な解説がほぼない領域です。

本記事では、ORA-00947: not enough values完全な原因と解決方法を、リファレンスとして実用的に整理します。10大発生パターン、姉妹エラー ORA-00913/ORA-01400 との比較、5つの解決策、CTAS 後の対処、Rails/Java/Python 対応、実践シナリオ、FAQまで完全網羅。この1本で ORA-00947 を根本から解決できるようになります。


目次

結論:列数と値の数を一致させる

時間がない方向けに、最速の対処を先に示します。

エラーメッセージの読み方

ORA-00947: not enough values
                ↑ 
            値が足りない
            = 期待される数より少ない

期待される数:

  • INSERT の列リスト(または全列)の数
  • タプル (A, B, C) の要素数
  • OBJECT 型のコンストラクタの引数数

最速の診断

-- ① テーブルの列数確認
SELECT column_name, data_type, nullable, data_default
FROM user_tab_columns 
WHERE table_name = 'EMPLOYEES'
ORDER BY column_id;

-- ② サブクエリの列数
SELECT * FROM (
  <サブクエリ>
) x WHERE 1=0;
-- 実行して列数を目視

5つの解決策

#手法使う場面
列リスト明示INSERT の保守性
値を追加単純な不足
NULL / DEFAULT省略したい列
サブクエリ調整INSERT SELECT / IN
OBJECT 型対応コンストラクタ

姉妹エラー3種

エラー意味
ORA-00947値が足りない
ORA-00913値が多すぎる
ORA-01400NOT NULL に NULL

INSERT エラー3兄弟

詳細は以下で解説します。


Oracle の INSERT ルール

INSERT の基本構文

列リスト明示:

INSERT INTO t (col1, col2, col3) VALUES (v1, v2, v3);
-- 列数 = 値数 が必須

列リスト省略:

INSERT INTO t VALUES (v1, v2, v3);
-- テーブルの全列(宣言順)に対応
-- 全列分の値が必要

INSERT … SELECT:

INSERT INTO t (col1, col2) SELECT c1, c2 FROM other;
-- 列数 = SELECT の列数

タプル比較

-- (A, B) IN (SELECT ...) はタプル比較
SELECT * FROM t 
WHERE (col1, col2) IN (SELECT c1, c2 FROM other);
-- サブクエリも 2 列必要

【原因①】INSERT VALUES で値不足(最頻出)

症状

CREATE TABLE employees (
  id NUMBER, 
  name VARCHAR2(100), 
  salary NUMBER,
  dept_id NUMBER
);

-- ❌ 4列に3値
INSERT INTO employees(id, name, salary, dept_id) 
VALUES (100, 'John', 50000);
-- ORA-00947

解決

A. 値を追加:

INSERT INTO employees(id, name, salary, dept_id) 
VALUES (100, 'John', 50000, 10);

B. 列リストを絞る:

INSERT INTO employees(id, name, salary) 
VALUES (100, 'John', 50000);
-- dept_id は NULL / DEFAULT

C. NULL 明示:

INSERT INTO employees(id, name, salary, dept_id) 
VALUES (100, 'John', 50000, NULL);

D. DEFAULT キーワード:

INSERT INTO employees(id, name, salary, dept_id) 
VALUES (100, 'John', 50000, DEFAULT);

【原因②】列リスト省略の罠

症状(超危険)

CREATE TABLE t (a NUMBER, b NUMBER, c NUMBER);

-- ❌ 列リストなし + 値不足
INSERT INTO t VALUES (1, 2);
-- ORA-00947(3列に2値)

CTAS 後のカラム追加

-- 一度動くコード
CREATE TABLE t (a NUMBER, b NUMBER, c NUMBER);
INSERT INTO t VALUES (1, 2, 3);  -- OK

-- カラム追加後
ALTER TABLE t ADD (d NUMBER);
INSERT INTO t VALUES (1, 2, 3);  -- ORA-00947(4列に3値)

解決:常に列リスト明示(推奨)

-- ✅ 明示的な列リスト
INSERT INTO t (a, b, c) VALUES (1, 2, 3);
-- カラム追加でも壊れない

保守性のため列リスト必須


【原因③】INSERT … SELECT の列不足

症状

CREATE TABLE dst (a NUMBER, b NUMBER, c NUMBER);
CREATE TABLE src (a NUMBER, b NUMBER);

-- ❌ SELECT が 2 列、INSERT は 3 列想定
INSERT INTO dst SELECT * FROM src;
-- ORA-00947

解決

A. 列リスト明示:

INSERT INTO dst (a, b) SELECT * FROM src;
-- c は NULL

B. NULL 明示:

INSERT INTO dst SELECT a, b, NULL FROM src;

C. 定数明示:

INSERT INTO dst SELECT a, b, 0 FROM src;

【原因④】タプル比較の失敗(超危険)

症状

-- ❌ 2要素タプル、サブクエリは1列
SELECT * FROM employees 
WHERE (dept_id, salary) IN (SELECT dept_id FROM departments);
-- ORA-00947

解決

A. サブクエリを2列に:

SELECT * FROM employees 
WHERE (dept_id, salary) IN (
  SELECT dept_id, min_salary FROM departments
);

B. 単純な IN に変更:

SELECT * FROM employees 
WHERE dept_id IN (SELECT dept_id FROM departments);

HAVING でも同様

-- ❌ タプル比較
SELECT dept_id, COUNT(*) FROM employees
GROUP BY dept_id
HAVING (dept_id, COUNT(*)) IN (SELECT dept_id FROM departments);
-- ORA-00947

修正:

SELECT dept_id, COUNT(*) FROM employees
GROUP BY dept_id
HAVING dept_id IN (SELECT dept_id FROM departments);

【原因⑤】CTAS 後の一括修正

シナリオ

CREATE TABLE src (a, b, c);
CREATE TABLE dst AS SELECT * FROM src WHERE 1=0;
-- dst は 3 列

ALTER TABLE dst ADD gender CHAR(1) DEFAULT 'M' NOT NULL;
-- dst は 4 列

INSERT INTO dst SELECT * FROM src;
-- ORA-00947(3列を4列に)

解決

INSERT INTO dst (a, b, c) SELECT * FROM src;
-- gender は DEFAULT 'M'

または NOT NULL に注意:

-- gender が NOT NULL 明示なら
INSERT INTO dst (a, b, c, gender) SELECT a, b, c, 'F' FROM src;

NOT NULL 関連は ORA-01400: cannot insert NULL の記事も参照してください。


【原因⑥】OBJECT 型のコンストラクタ

症状

CREATE TYPE address_t AS OBJECT (
  street VARCHAR2(100),
  city VARCHAR2(50),
  zip VARCHAR2(10)
);

CREATE TABLE customers (
  id NUMBER,
  addr address_t
);

-- ❌ コンストラクタの引数不足
INSERT INTO customers VALUES (1, address_t('Main St', 'Tokyo'));
-- ORA-00947(3引数のところ2引数)

解決

INSERT INTO customers VALUES 
(1, address_t('Main St', 'Tokyo', '100-0001'));

【原因⑦】MERGE 文の INSERT 節

症状

MERGE INTO dst USING src ON (dst.id = src.id)
WHEN NOT MATCHED THEN 
  INSERT (id, name, salary) VALUES (src.id, src.name);
-- ORA-00947(3列に2値)

解決

MERGE INTO dst USING src ON (dst.id = src.id)
WHEN NOT MATCHED THEN 
  INSERT (id, name, salary) VALUES (src.id, src.name, src.salary);

MERGE の詳細は Oracle MERGE 文 使い方の記事も参照してください。


【原因⑧】動的 SQL のミス

症状

DECLARE
  v_cols VARCHAR2(100) := 'id, name, salary';
  v_vals VARCHAR2(100) := ':1, :2';  -- 1つ足りない
  v_sql VARCHAR2(1000);
BEGIN
  v_sql := 'INSERT INTO t (' || v_cols || ') VALUES (' || v_vals || ')';
  EXECUTE IMMEDIATE v_sql USING 100, 'John';
-- ORA-00947
END;
/

解決

動的 SQL 前に列数チェック:

IF LENGTH(v_cols) - LENGTH(REPLACE(v_cols, ',', '')) 
   != LENGTH(v_vals) - LENGTH(REPLACE(v_vals, ',', '')) THEN
  RAISE_APPLICATION_ERROR(-20001, '列数と値数不一致');
END IF;

【原因⑨】Rails マイグレーション後

シナリオ

# Rails migration でカラム追加
class AddDeptToUsers < ActiveRecord::Migration[8.0]
  def change
    add_column :users, :dept_id, :integer
  end
end

その後、生 SQL で INSERT していると:

# ❌ カラム追加を忘れて
ActiveRecord::Base.connection.execute("INSERT INTO users VALUES (1, 'John')")
-- ORA-00947

解決

A. ActiveRecord 使用:

User.create!(id: 1, name: 'John')
# Rails が列名を自動処理

B. 列リスト明示:

ActiveRecord::Base.connection.execute(
  "INSERT INTO users (id, name) VALUES (1, 'John')"
)

Rails 8 系の詳細は Rails 8 アップグレードガイドの記事、find/find_by/where の記事、rails db:migrate 使い方の記事も参照してください。


【原因⑩】RETURNING 変数不足

症状

DECLARE
  v_id NUMBER;
BEGIN
  INSERT INTO t (a, b, c) VALUES (1, 2, 3)
  RETURNING a, b, c INTO v_id;
  -- ORA-00947(3列を1変数に)
END;
/

解決

DECLARE
  v_a NUMBER; v_b NUMBER; v_c NUMBER;
BEGIN
  INSERT INTO t (a, b, c) VALUES (1, 2, 3)
  RETURNING a, b, c INTO v_a, v_b, v_c;
END;
/

5つの解決策 完全リファレンス

解決策① 列リスト明示(保守性)

-- ✅ 常に列リスト
INSERT INTO t (col1, col2) VALUES (v1, v2);

-- ❌ 列リスト省略は危険
INSERT INTO t VALUES (v1, v2);

カラム追加でも壊れない

解決策② 値を追加

INSERT INTO t (a, b, c) VALUES (1, 2, 3);
-- 全ての列に値

解決策③ NULL / DEFAULT

-- NULL 明示
INSERT INTO t (a, b, c) VALUES (1, 2, NULL);

-- DEFAULT
INSERT INTO t (a, b, c) VALUES (1, 2, DEFAULT);

-- または列リストから除外
INSERT INTO t (a, b) VALUES (1, 2);
-- c は DEFAULT

解決策④ サブクエリ調整

-- 列数を合わせる
INSERT INTO dst (a, b, c) 
SELECT a, b, NULL FROM src;

-- タプル比較
WHERE (a, b) IN (SELECT c1, c2 FROM t);

解決策⑤ OBJECT 型対応

-- 全引数指定
INSERT INTO t VALUES (address_t(v1, v2, v3));

Rails / Java / Python 対応

Rails ActiveRecord

基本:

# ✅ ActiveRecord は自動処理
User.create!(id: 1, name: 'John')

# エラーハンドリング
begin
  User.create!(attributes)
rescue ActiveRecord::StatementInvalid => e
  if e.message.include?("ORA-00947")
    Rails.logger.error "値不足: #{e.message}"
  end
end

マイグレーション後の生 SQL:

# ❌ 列リスト省略
execute "INSERT INTO users VALUES (1, 'John')"

# ✅ 列リスト明示
execute "INSERT INTO users (id, name) VALUES (1, 'John')"

Java (JDBC)

// ✅ 列リスト明示 + PreparedStatement
PreparedStatement ps = conn.prepareStatement(
    "INSERT INTO users (id, name) VALUES (?, ?)"
);
ps.setInt(1, 1);
ps.setString(2, "John");
ps.executeUpdate();

// エラーハンドリング
try {
    ps.executeUpdate();
} catch (SQLException e) {
    if (e.getErrorCode() == 947) {
        logger.error("値不足: " + e.getMessage());
    }
}

Python (oracledb)

import oracledb

# ✅ 列リスト明示
cursor.execute("""
    INSERT INTO users (id, name) VALUES (:1, :2)
""", (1, 'John'))

# エラーハンドリング
try:
    cursor.execute(sql, params)
except oracledb.DatabaseError as e:
    error_obj, = e.args
    if error_obj.code == 947:
        print(f"値不足: {error_obj.message}")

実践シナリオ

シナリオ1:CTAS 後の運用

-- 1. テーブル作成
CREATE TABLE monthly_report AS 
SELECT id, name, amount FROM orders WHERE 1=0;

-- 2. データ投入(列リスト明示)
INSERT INTO monthly_report (id, name, amount) 
SELECT id, name, amount FROM orders WHERE created_at > SYSDATE - 30;

-- 3. 後日カラム追加
ALTER TABLE monthly_report ADD (region VARCHAR2(20));

-- 4. 既存 INSERT はそのまま動く
INSERT INTO monthly_report (id, name, amount) 
SELECT id, name, amount FROM orders WHERE created_at > SYSDATE - 30;

シナリオ2:ETL パイプラインでの防御

CREATE OR REPLACE PROCEDURE etl_load IS
  v_col_count PLS_INTEGER;
BEGIN
  -- 事前チェック
  SELECT COUNT(*) INTO v_col_count 
  FROM user_tab_columns 
  WHERE table_name = 'DST';
  
  IF v_col_count != 3 THEN
    RAISE_APPLICATION_ERROR(-20001, 
      '想定と列数不一致: ' || v_col_count);
  END IF;
  
  -- 実際の ETL
  INSERT INTO dst (a, b, c) SELECT * FROM src;
END;
/

シナリオ3:Rails 検証

class Order < ApplicationRecord
  # 属性の存在確認
  validates :id, :name, :amount, presence: true
  
  before_create :validate_columns
  
  private
  
  def validate_columns
    required = %w[id name amount]
    missing = required - attributes.keys
    if missing.any?
      errors.add(:base, "必須列不足: #{missing}")
      throw :abort
    end
  end
end

シナリオ4:Data Pump インポート

# 事前に列数一致確認
impdp system/pw dumpfile=data.dmp \
  TABLE_EXISTS_ACTION=APPEND

# 列不一致で失敗する場合、SQLFILE で確認
impdp system/pw dumpfile=data.dmp SQLFILE=preview.sql

Data Pump の詳細は Oracle Data Pump 使い方の記事も参照してください。

シナリオ5:CI/CD での事前検証

# .github/workflows/db-check.yml
- name: Check schema consistency
  run: |
    sqlplus -s $DB_USER/$DB_PW <<EOF
    SELECT table_name, COUNT(*) col_count
    FROM user_tab_columns
    WHERE table_name IN ('USERS', 'ORDERS')
    GROUP BY table_name;
    EOF

Kamal 2 デプロイの詳細は Kamal 2 デプロイの記事を参照してください。

シナリオ6:MERGE での確実な INSERT

MERGE INTO users u
USING (SELECT 1 id, 'John' name, 100 salary FROM DUAL) s
ON (u.id = s.id)
WHEN NOT MATCHED THEN
  INSERT (id, name, salary) VALUES (s.id, s.name, s.salary);
-- 全列明示

シナリオ7:Java Spring での対処

@Repository
public class UserRepo {
    @Autowired
    private JdbcTemplate jdbc;
    
    public void insertUser(User user) {
        // 常に列リスト明示
        String sql = """
            INSERT INTO users (id, name, salary) 
            VALUES (?, ?, ?)
            """;
        jdbc.update(sql, user.getId(), user.getName(), user.getSalary());
    }
}

シナリオ8:Python での動的 INSERT

def insert_row(cursor, table: str, data: dict):
    columns = list(data.keys())
    placeholders = [f":{i+1}" for i in range(len(columns))]
    
    sql = f"""
        INSERT INTO {table} ({', '.join(columns)})
        VALUES ({', '.join(placeholders)})
    """
    cursor.execute(sql, list(data.values()))

# 使用
insert_row(cursor, "users", {"id": 1, "name": "John"})

シナリオ9:Docker Oracle でのテスト

docker exec -it oracle-xe sqlplus scott/tiger <<EOF
CREATE TABLE t (a NUMBER, b NUMBER, c NUMBER);

-- ORA-00947
INSERT INTO t VALUES (1, 2);

-- OK
INSERT INTO t (a, b) VALUES (1, 2);
EOF

Docker 関連は docker daemon 接続エラーの記事、Docker no space left on device の記事も参照してください。

シナリオ10:Autonomous DB

-- Autonomous DB でも同じ動作
-- 23ai: DEFAULT ON NULL 対応で柔軟に
ALTER TABLE t MODIFY col DEFAULT ON NULL 0;

トラブルシューティング

列リスト明示すべきタイミング

常に明示推奨。特に:

  • 本番のスクリプト
  • マイグレーション
  • ETL

エラー位置が分からない

-- 列数目視
SELECT column_id, column_name, data_type
FROM user_tab_columns 
WHERE table_name = 'T'
ORDER BY column_id;

動的 SQL でのデバッグ

DBMS_OUTPUT.PUT_LINE('SQL: ' || v_sql);
-- 生成 SQL を出力

CTAS で列がずれた

-- 元テーブル
DESC src;
-- コピー先
DESC dst;
-- 差分確認
SELECT column_name FROM user_tab_columns WHERE table_name = 'SRC'
MINUS
SELECT column_name FROM user_tab_columns WHERE table_name = 'DST';

PostgreSQL からの移行

PG の INSERT INTO t DEFAULT VALUES は Oracle 未対応。列名 + DEFAULT 明示。

RETURNING の変数不足

RETURNING a, b, c INTO v_a, v_b, v_c;
-- 返る列数と変数数を一致

よくある質問(FAQ)

Q1. ORA-00947 と ORA-00913 の違い

  • 00947: 値が足りない
  • 00913: 値が多すぎる

Q2. ORA-01400 との違い

  • 01400: NOT NULL 列に NULL を入れた
  • 00947: 列自体が足りない

Q3. 列リスト省略の是非

明示推奨。テーブル変更に強い。

Q4. Rails での自動対応

ActiveRecord::Base は自動で列名処理、心配なし。

Q5. タプル比較の使い所

複数列での一致検索、注意深く。

Q6. Object Type

全引数指定必須。または DEFAULT コンストラクタ定義。

Q7. カラム追加時の影響

列リスト明示なら影響なし。省略なら要修正。

Q8. 動的 SQL

生成 SQL を出力して確認。

Q9. パフォーマンスへの影響

構文エラーは実行前、パフォーマンス影響なし。

Q10. 予防策

  • 列リスト明示
  • テストデータの CI 検証
  • コードレビュー

Q11. Data Pump での対処

TABLE_EXISTS_ACTION=APPEND + 列不一致はSQLFILEで確認。

Q12. 23ai の新機能

DEFAULT ON NULL で柔軟なデフォルト値。


参考リンク

Oracle 公式


まとめ

ORA-00947: not enough values の要点を再整理します。

エラーの本質

SQL 文で期待される値の数より
実際の値の数が少ない
→ INSERT / タプル比較 / OBJECT コンストラクタ

エラーメッセージの読み方

ORA-00947: not enough values
                ↑
        期待より値が足りない

INSERT エラー3兄弟

エラー意味
ORA-00947値が足りない
ORA-00913値が多すぎる
ORA-01400NOT NULL に NULL

10大原因

#原因対処
INSERT VALUES 値不足値追加 or 列リスト
列リスト省略の罠列リスト明示
INSERT SELECT 列不足列合わせ
タプル比較サブクエリ列数
CTAS 後の追加列リスト明示
OBJECT コンストラクタ全引数
MERGE INSERT全列指定
動的 SQL生成確認
Rails マイグレーション後ActiveRecord
RETURNING 変数不足変数数一致

5つの解決策

-- ① 列リスト明示(推奨)
INSERT INTO t (a, b) VALUES (1, 2);

-- ② 値追加
INSERT INTO t (a, b, c) VALUES (1, 2, 3);

-- ③ NULL / DEFAULT
INSERT INTO t (a, b, c) VALUES (1, 2, NULL);
INSERT INTO t (a, b, c) VALUES (1, 2, DEFAULT);

-- ④ サブクエリ調整
INSERT INTO dst (a, b, c) SELECT a, b, NULL FROM src;

-- ⑤ OBJECT 型対応
INSERT INTO t VALUES (obj_t(v1, v2, v3));

診断クエリ

-- ① 列構造確認
SELECT column_id, column_name, data_type, nullable, data_default
FROM user_tab_columns 
WHERE table_name = 'T'
ORDER BY column_id;

-- ② サブクエリ列数
SELECT * FROM (<subquery>) WHERE 1=0;

タプル比較の落とし穴

-- ❌ タプル要素数不一致
WHERE (a, b) IN (SELECT c FROM t);   -- ORA-00947

-- ✅ 要素数合わせ
WHERE (a, b) IN (SELECT c1, c2 FROM t);

列リスト明示の重要性

✅ 常に列リスト明示
- テーブル変更に強い
- 保守性向上
- CTAS 後もOK
- 意図が明確

❌ 列リスト省略
- カラム追加で壊れる
- 順序に依存
- 意図が不明確

予防のポイント

1. INSERT には必ず列リスト明示
2. INSERT SELECT でも列を明示
3. Rails: ActiveRecord 使用
4. Java: PreparedStatement + 列リスト
5. Python: バインド変数 + 列リスト
6. CI/CD でスキーマ検証
7. カラム追加時は既存 SQL 確認
8. Data Pump は SQLFILE で事前確認
9. コードレビューで INSERT 確認
10. Object Type は全引数指定

これらの知識は、Oracle でのアプリ開発・ETL パイプライン・マイグレーション設計・Rails / Java / Python 開発・データ移行・CTAS 運用・CI/CD 検証など、あらゆる場面で活用できます。本記事をブックマークしておけば、ORA-00947 に出会っても冷静に的確に対処できるようになります。


本記事は2026年6月時点の情報をもとに、Oracle Database 19c〜23ai での動作確認・公式ドキュメントに基づき作成しています。Oracle のバージョンにより挙動が異なる場合があるため、最新の情報は Oracle 公式ドキュメント(docs.oracle.com)もあわせてご確認ください。