【Oracle】ORA-01847の原因と解決方法|day of month must be between 1 and last day of month・存在しない日付の直し方

【Oracle】ORA-01847の原因と解決方法|day of month must be between 1 and last day of month・存在しない日付の直し方 Oracle

ORA-01847: day of month must be between 1 and last day of month は、Oracleで存在しない日付をDATE型へ変換しようとした時に発生するエラーです。代表例は 2026-02-302026-04-312025-02-29 のように、月の日数を超えた日を指定しているケースです。

Oracle公式のエラーメッセージでも、指定した日がその月で有効な範囲にないことが原因とされています。フォーマット文字列が間違っているだけでなく、データそのものが実在しない日付になっている点が重要です。

先に結論
ORA-01847は、日付フォーマットよりも「その年月にその日が存在するか」を確認するエラーです。2月30日、4月31日、うるう年ではない年の2月29日、月末日を固定で31日にした処理が典型例です。CSV取込やバッチ生成日付では、変換前の文字列を隔離して原因データを特定します。
スポンサーリンク

ORA-01847とは

ORA-01847は、日付の「形」は日付らしく見えていても、カレンダー上は存在しない場合に出ます。2026-13-01 のように月が不正なら ORA-01843、数字であるべき場所に文字が入るなら ORA-01858、フォーマット指定と文字列全体の長さが合わないなら ORA-01830 が出やすくなります。

ORA-01847では、月は読み取れており、日も数値として読めています。しかし、指定した月の最終日を超えているため、Oracleが有効なDATE値として作れません。

2026-02-30

2月に30日はないためORA-01847になります。

2026-04-31

4月は30日までなので、31日は存在しません。

2025-02-29

2025年はうるう年ではないため、2月29日は存在しません。

2024-02-29

2024年はうるう年なので、この日付は有効です。

すぐ再現できる例

次のSQLは、存在しない日付を TO_DATE でDATE型に変換しようとしているため、ORA-01847になります。

ora01847-invalid-date.sql
SELECT TO_DATE('2026-02-30', 'YYYY-MM-DD') AS target_date
FROM dual;

-- ORA-01847: day of month must be between 1 and last day of month

フォーマットモデル YYYY-MM-DD 自体は間違っていません。問題は 2026-02-30 という日付が実在しないことです。TO_DATEの書式モデル全体を確認したい場合は TO_CHAR / TO_DATEの日付フォーマット記事 も参考になります。

よくある原因

月末日を31日固定で作っている

毎月の締め日や対象期間を YYYYMM31 のように作ると、2月、4月、6月、9月、11月で失敗します。

うるう年を考慮していない

2月29日を固定で作る処理は、うるう年以外でORA-01847になります。

CSVや外部ファイルに不正日付が混ざっている

見た目は日付形式でも、2026/04/31 のように存在しない日付が混じると取込時に失敗します。

文字列結合で日付を組み立てている

年、月、日を別々の項目から連結して TO_DATE している場合、組み合わせで不正日付が作られることがあります。

暗黙変換に任せている

DATE列と文字列を比較した時に、NLS設定に依存して暗黙変換が走り、想定外の行でエラーになることがあります。

月末日を正しく作る

月末日を扱う処理では、日を31固定で組み立てるのではなく、Oracleの LAST_DAY を使う方が安全です。LAST_DAY は指定した日付が属する月の最終日を返します。

use-last-day.sql
SELECT
    LAST_DAY(DATE '2026-02-01') AS end_of_feb,
    LAST_DAY(DATE '2026-04-01') AS end_of_apr,
    LAST_DAY(DATE '2024-02-01') AS leap_year_feb
FROM dual;

月初から月末までの範囲を作る場合は、月初を TRUNC で作り、月末を LAST_DAY で作ると分かりやすくなります。

month-range-safe.sql
SELECT
    TRUNC(:target_date, 'MM') AS month_start,
    LAST_DAY(:target_date)     AS month_end
FROM dual;
日付を文字列で組み立てない
year || month || '31' のような文字列結合は、月の日数を無視しやすい書き方です。月末日が欲しいだけなら、まずその月の1日をDATE型で作り、LAST_DAY を使う方が安全です。

CSV取込で原因行を見つける

CSVや外部ファイルの取込でORA-01847が出る場合、いきなり本テーブルのDATE列へ入れると原因行が見えにくくなります。まず文字列のままステージングテーブルへ取り込み、不正日付を探す流れにすると調査しやすいです。

staging-table.sql
CREATE TABLE import_orders_stg (
    line_no      NUMBER,
    order_date_s VARCHAR2(20),
    amount_s     VARCHAR2(30)
);

Oracle 12.2以降であれば、VALIDATE_CONVERSION を使ってDATE変換できない行を抽出できます。

find-invalid-date-rows.sql
SELECT line_no, order_date_s
FROM import_orders_stg
WHERE VALIDATE_CONVERSION(order_date_s AS DATE, 'YYYY-MM-DD') = 0;

VALIDATE_CONVERSION が使えない環境では、PL/SQLで変換を試し、失敗した行をエラーテーブルへ逃がす方法もあります。バージョンや権限により使える機能が違うため、取込処理では「不正行を捨てる」のではなく「不正行を残して原因を確認できる」形にしておくと安全です。

default-null-on-conversion-error.sql
SELECT
    line_no,
    order_date_s,
    TO_DATE(
        order_date_s DEFAULT NULL ON CONVERSION ERROR,
        'YYYY-MM-DD'
    ) AS converted_date
FROM import_orders_stg;

DEFAULT NULL ON CONVERSION ERROR を使うと、変換できない値をNULLとして扱えるため、原因行の抽出に使えます。ただし、業務データとしてNULLを許容するという意味ではありません。本登録前の検査やレポート用途で使い、問題行を修正してからDATE列へ登録します。

数値変換で似た考え方が必要な場合は ORA-01722の記事 も参考になります。DATE変換でも、直接変換して落とすより、まず原因データを見えるようにするのが実務では大切です。

暗黙変換を避ける

DATE列と文字列を直接比較すると、Oracleが暗黙的に文字列をDATEへ変換することがあります。この時、NLS_DATE_FORMATやデータ内容によって、思わぬ行でORA-01847が出ることがあります。

implicit-conversion-ng.sql
-- NG: 文字列が暗黙的にDATEへ変換される可能性がある
SELECT *
FROM orders
WHERE order_date = '2026-02-30';
explicit-date-ok.sql
-- OK: 日付リテラルや明示的なTO_DATEを使う
SELECT *
FROM orders
WHERE order_date = DATE '2026-02-28';

SELECT *
FROM orders
WHERE order_date = TO_DATE(:order_date_s, 'YYYY-MM-DD');

入力値がユーザー入力や外部連携値である場合は、アプリ側でも日付妥当性を確認し、DB側でもステージングや検証SQLで原因行を特定できるようにしておくと安全です。

バッチで日付を作る時の安全な書き方

月次バッチでは「対象月の月末」「前月末」「翌月1日」などを作る処理がよくあります。この時、文字列で YYYYMMDD を組み立てるより、DATE型の計算で進める方がORA-01847を避けやすくなります。

batch-date-range.sql
WITH params AS (
    SELECT DATE '2026-02-01' AS target_month FROM dual
)
SELECT
    TRUNC(target_month, 'MM')                  AS period_start,
    ADD_MONTHS(TRUNC(target_month, 'MM'), 1)   AS next_month_start,
    LAST_DAY(target_month)                     AS period_end
FROM params;

検索条件では、月末日の23:59:59を作るより、翌月1日未満で指定する方が扱いやすいことが多いです。

search-month-range.sql
SELECT *
FROM orders
WHERE order_date >= TRUNC(:target_month, 'MM')
  AND order_date <  ADD_MONTHS(TRUNC(:target_month, 'MM'), 1);

アプリ側で先に弾く

ORA-01847はDBで検出できますが、ユーザー入力やAPIリクエストであれば、アプリ側でも存在しない日付を弾く方が親切です。たとえば「年」「月」「日」を別々に入力させる画面では、単に日が1から31の範囲にあるかを見るだけでは不十分です。対象年月の最終日を求めて、その範囲内かを検証します。

画面入力

月を選んだ時点で日付の選択肢を変えると、2月30日や4月31日を入力しにくくできます。

API

受け取った文字列をそのままDBへ渡さず、アプリ側で日付パースと妥当性検証を行います。

CSV取込

取込前チェックで不正日付を一覧化し、行番号と元の値を利用者へ返します。

バッチ

日付生成ロジックを共通化し、月末日はDB関数または言語標準の日付APIで作ります。

似た日付エラーとの違い

Oracleの日付変換エラーは似た番号が多いため、どこまで変換できているかで切り分けます。ORA-01847は、月と日は読めているが、その月にその日が存在しないケースです。

ORA-01847

日がその月の範囲外です。2月30日、4月31日、非うるう年の2月29日などが該当します。

ORA-01858

数字であるべき場所に文字が入っています。詳しくは ORA-01858の記事 を参照してください。

ORA-01830

日付フォーマットが文字列全体を変換しきれていません。詳しくは ORA-01830の記事 を参照してください。

ORA-01843

月が無効です。詳しくは ORA-01843の記事 を参照してください。

ORA-01861

文字列と日付フォーマットが一致していません。詳しくは ORA-01861の記事 を参照してください。

対応手順まとめ

  1. エラーになった日付文字列を確認する
  2. 2月30日、4月31日、非うるう年の2月29日など、存在しない日付がないか確認する
  3. 月末日を31固定で作っていないか確認する
  4. CSV取込では、文字列のままステージングして不正行を抽出する
  5. DATE列との比較で暗黙変換が起きていないか確認する
  6. 月末日は LAST_DAY、月初は TRUNC(date, 'MM') を使う
  7. 検索条件は翌月1日未満の範囲指定にして、時刻込みDATEにも対応する

ORA-01847は、フォーマット指定を見直すだけでは直らないことがあります。本当に見るべきなのは、変換対象の文字列がカレンダー上存在する日付かどうかです。月末日や締め日を扱う処理では、日付を文字列として組み立てず、DATE型の計算で作るようにすると再発を防ぎやすくなります。

よくある質問

ORA-01847はフォーマット指定ミスですか?

フォーマット指定ミスが関係することもありますが、主原因は存在しない日付です。YYYY-MM-DD が正しくても、2026-02-30 はDATE型にできません。

2月末を作りたい時はどうすればよいですか?

LAST_DAY(DATE '2026-02-01') のように、対象月の任意の日付から LAST_DAY を使います。31日固定で文字列を作る方法は避けます。

CSVのどの行が悪いか分かりません

いったん文字列列を持つステージングテーブルへ取り込み、VALIDATE_CONVERSION などで変換できない行を抽出します。直接DATE列へ入れると、どの行で落ちたか追いにくくなります。

ORA-01839との違いは何ですか?

どちらも存在しない日付に近いエラーですが、環境や変換経路によって表示される番号が異なることがあります。実務上は、対象年月にその日が存在するか、月末日計算が正しいかを確認する点が共通です。

参考