【Oracle】ORA-02290の原因と解決方法|CHECK制約違反の直し方

【Oracle】ORA-02290の原因と解決方法|CHECK制約違反の直し方 Oracle

ORA-02290: check constraint violated は、Oracleで INSERTUPDATE しようとした値が、テーブルに定義された CHECK 制約の条件を満たさない時に発生するエラーです。

たとえば、ステータス列は 'ACTIVE' または 'INACTIVE' のみ、金額は0以上、区分は決められた値のみ、というルールがCHECK制約で定義されている場合、その条件から外れた値を入れるとORA-02290になります。

先に結論
エラーに表示された制約名を使って USER_CONSTRAINTS / ALL_CONSTRAINTS を検索し、SEARCH_CONDITION の条件を確認します。そのうえで、投入値を直す、制約定義を見直す、既存データを修正する、の順に判断します。
スポンサーリンク

ORA-02290とは

Oracle公式のエラー説明では、ORA-02290は挿入しようとした値が名前付きCHECK制約を満たさない場合に発生します。対処は、制約に違反する値を登録しないことです。

制約全体の基本は 制約(CONSTRAINT)完全ガイド、INSERT/UPDATEの基本は INSERT・UPDATE・DELETE完全ガイド も参考になります。

まず制約名を確認する

ORA-02290のメッセージには、通常 (スキーマ名.制約名) が表示されます。この制約名から、どのテーブルのどんな条件に違反したのかを調べます。

ora-02290-message.txt
ORA-02290: check constraint (APP.CK_ORDER_STATUS) violated
find-check-constraint.sql
SELECT owner,
       table_name,
       constraint_name,
       constraint_type,
       status,
       validated,
       search_condition
FROM all_constraints
WHERE owner = UPPER('&OWNER')
  AND constraint_name = UPPER('&CONSTRAINT_NAME');

SEARCH_CONDITIONが見づらい場合

環境やクライアントによっては、SEARCH_CONDITION が長くて見づらい、または LONG 型の扱いで取得しづらいことがあります。SQL*PlusやSQLclでは SET LONG を大きめにしてから確認します。

show-search-condition-sqlplus.sql
SET LONG 100000
SET PAGESIZE 100
SET LINESIZE 200

SELECT constraint_name,
       search_condition
FROM user_constraints
WHERE constraint_name = UPPER('&CONSTRAINT_NAME');

新しい環境では SEARCH_CONDITION_VC が使える場合もあります。短い条件であればこちらの方が扱いやすいです。

show-search-condition-vc.sql
SELECT constraint_name,
       search_condition_vc
FROM user_constraints
WHERE constraint_name = UPPER('&CONSTRAINT_NAME');

制約列を確認する

CHECK制約は複数列を使っている場合があります。制約名だけでなく、どの列が関係しているかも確認します。

find-check-constraint-columns.sql
SELECT c.owner,
       c.table_name,
       c.constraint_name,
       cc.column_name,
       cc.position,
       c.search_condition
FROM all_constraints c
LEFT JOIN all_cons_columns cc
  ON cc.owner = c.owner
 AND cc.constraint_name = c.constraint_name
WHERE c.owner = UPPER('&OWNER')
  AND c.constraint_name = UPPER('&CONSTRAINT_NAME')
ORDER BY cc.position;

constraint_typeC の場合、CHECK制約またはNOT NULL制約系の制約です。NOT NULL違反は通常 ORA-01400 で見ることが多いため、必要に応じて ORA-01400 完全ガイド も確認してください。

最小の再現例

次のテーブルでは、statusACTIVE または INACTIVE のみ許可されます。

create-check-constraint-example.sql
CREATE TABLE app_user_status (
  user_id NUMBER PRIMARY KEY,
  status  VARCHAR2(20),
  CONSTRAINT ck_user_status
    CHECK (status IN ('ACTIVE', 'INACTIVE'))
);
bad-insert-check-constraint.sql
INSERT INTO app_user_status (user_id, status)
VALUES (1, 'STOPPED');

-- ORA-02290: check constraint (APP.CK_USER_STATUS) violated
fixed-insert-check-constraint.sql
INSERT INTO app_user_status (user_id, status)
VALUES (1, 'ACTIVE');

テーブル作成時の制約定義は CREATE TABLE完全ガイド も参考になります。

発生しやすいパターン

区分値が定義外

status IN ('A','I') のような条件に対して、画面やCSVから未定義のコードが入るケースです。

数値の範囲外

amount >= 0rate BETWEEN 0 AND 100 などの条件に違反するケースです。

日付の範囲外

開始日と終了日の関係、未来日禁止、基準日以降のみ許可などの条件に違反するケースです。

複数列の組み合わせ違反

end_date IS NULL OR end_date >= start_date のように、単一列ではなく行全体の整合性を見ているケースです。

NLSや文字大小の違い

大文字のみ許可しているのに小文字が来る、日付文字列の解釈が環境で違う、などのケースです。

違反データを探すSQL

制約条件が分かったら、その条件を反転して違反データを探します。既存データ修正や、制約を有効化できない原因調査にも使えます。

find-invalid-status-rows.sql
SELECT user_id,
       status
FROM app_user_status
WHERE status NOT IN ('ACTIVE', 'INACTIVE')
   OR status IS NULL;
find-invalid-range-rows.sql
SELECT order_id,
       amount,
       discount_rate
FROM orders
WHERE amount < 0
   OR discount_rate < 0
   OR discount_rate > 100;
条件の反転は慎重に書く
CHECK制約は NULL を含むと三値論理の影響を受けます。単純に NOT (条件) と書くだけでは、NULLを含む行の扱いが意図とずれることがあります。

NULLはCHECK制約に通ることがある

OracleのCHECK制約は、条件が FALSE になった場合に違反します。条件が TRUE または UNKNOWN の場合は違反になりません。そのため、列がNULLの時はCHECK制約を通ることがあります。

check-null-behavior.sql
CREATE TABLE check_null_sample (
  amount NUMBER,
  CONSTRAINT ck_amount_positive CHECK (amount > 0)
);

-- amount > 0 は UNKNOWN になるため、CHECK制約だけなら通る
INSERT INTO check_null_sample (amount) VALUES (NULL);

-- NULLも禁止したい場合
ALTER TABLE check_null_sample MODIFY amount NOT NULL;

NULLを禁止したい場合は、CHECK制約だけに頼らず NOT NULL 制約も定義します。

制約を一時的に無効化してよいか

移行作業や一括修正のためにCHECK制約を一時的に無効化することはできます。ただし、無効化中に不正データが入ると、再有効化時に失敗します。本番では、無効化前後のデータ検証手順を必ず用意します。

disable-enable-check-constraint.sql
-- 一時的に無効化
ALTER TABLE app_user_status DISABLE CONSTRAINT ck_user_status;

-- データ修正後、既存データも検証して有効化
ALTER TABLE app_user_status ENABLE VALIDATE CONSTRAINT ck_user_status;

-- 既存データは検証せず、以後のDMLだけ制約する場合
ALTER TABLE app_user_status ENABLE NOVALIDATE CONSTRAINT ck_user_status;

ENABLE VALIDATEENABLE NOVALIDATE の違いは、既存データを検証するかどうかです。制約の有効化/無効化は便利ですが、データ品質に直結するため運用ルールを決めて使います。

大量取込ではLOG ERRORSで違反行を分ける

CSV取込や大量INSERTで一部の行だけORA-02290になる場合、処理全体を止めるより、DBMS_ERRLOGLOG ERRORS でエラー行を別表へ退避する方法があります。後から違反行だけを確認・修正できるため、データ移行や一括取込で有効です。

log-errors-check-constraint.sql
BEGIN
  DBMS_ERRLOG.CREATE_ERROR_LOG(dml_table_name => 'APP_USER_STATUS');
END;
/

INSERT INTO app_user_status (user_id, status)
SELECT user_id, status
FROM staging_user_status
LOG ERRORS INTO err$_app_user_status ('ORA-02290') REJECT LIMIT UNLIMITED;

DBMS_ERRLOG の詳しい使い方は エラーログを自動収集するDBMS_ERRLOGの使い方 を参照してください。

制約名がSYS_C…で分かりにくい場合

制約名を明示せずに作成すると、SYS_C0012345 のような自動名になります。この場合でも ALL_CONSTRAINTS から条件は確認できますが、運用時には意味のある名前を付ける方が調査しやすくなります。

rename-check-constraint.sql
ALTER TABLE app_user_status
RENAME CONSTRAINT sys_c0012345 TO ck_user_status;

関連エラーとの違い

ORA-02290

CHECK制約の条件に違反しています。制約名から SEARCH_CONDITION を確認します。

ORA-02291

外部キー制約で、参照先の親キーが存在しないエラーです。詳細は ORA-02291の原因と解決方法 を参照してください。

ORA-02292

親レコードを削除しようとしたが、子レコードが存在するエラーです。詳細は ORA-02292の原因と解決方法 を参照してください。

ORA-01438

数値列の精度や桁数を超えたエラーです。CHECK制約ではなく列定義そのものを確認します。詳細は ORA-01438の原因と解決方法 を参照してください。

ORA-00001

UNIQUE制約や主キー制約に違反しています。重複データを確認します。詳細は ORA-00001の原因と解決方法 を参照してください。

修正の流れ

  1. ORA-02290のメッセージから制約名を確認する
  2. ALL_CONSTRAINTS または USER_CONSTRAINTSSEARCH_CONDITION を確認する
  3. ALL_CONS_COLUMNS で関係する列を確認する
  4. INSERT/UPDATEしようとしている値が条件を満たすか確認する
  5. 既存データが原因なら、条件を反転したSQLで違反行を洗い出す
  6. 仕様変更なら、データを直すのか制約定義を変えるのかを決める
  7. 制約を無効化する場合は、再有効化時の検証手順を用意する

よくある質問

どの列が原因かエラーだけで分かりますか?

ORA-02290のエラーには制約名が表示されます。その制約名を ALL_CONSTRAINTSALL_CONS_COLUMNS で調べると、条件と関連列を確認できます。

NULLを入れたのにCHECK制約で止まりません

CHECK制約は条件がFALSEの時に違反します。NULLによりUNKNOWNになる条件は通ることがあります。NULLも禁止したい場合は NOT NULL を併用します。

制約を無効化して取り込んでもよいですか?

移行作業では選択肢になりますが、不正データが入るリスクがあります。取り込み後に違反データを修正し、ENABLE VALIDATE で再検証する運用にします。

まとめ

ORA-02290は、INSERTやUPDATEの値がCHECK制約の条件を満たさない時に発生します。まずエラーに表示された制約名を使い、SEARCH_CONDITION と関係列を確認します。

値を直すだけで解決する場合もありますが、仕様変更、既存データ、NULLの扱い、NLS、制約定義そのものが原因のこともあります。制約を無効化する場合は、必ず再有効化とデータ検証まで含めて対応してください。

参考