Oracleデータベースで月末日を取得するには、LAST_DAY関数を使います。当月末・前月末・翌月末の取得はもちろん、月初日の算出や月末判定、うるう年の自動対応まで、日付処理の重要な場面で活躍する関数です。
この記事では、LAST_DAY関数の基本構文から、ADD_MONTHS・TRUNCとの組み合わせ、WHERE句での月末データ抽出、月次締め処理などの実務パターンまで、網羅的に解説します。
この記事で学べること
- LAST_DAY関数の基本構文と戻り値
- 当月末・前月末・翌月末の取得方法
- 月初日の取得(TRUNC + ADD_MONTHS)
- うるう年2月末の自動判定
- 今日が月末かどうかの月末判定
- 月末のN営業日前を取得する方法
- ADD_MONTHSとの組み合わせパターン
- WHERE句での月末データ抽出
- 他RDBMS比較(MySQL・PostgreSQL・SQL Server)
- よくあるエラーと対処法
- 実務パターン(月次締め処理等)
LAST_DAY関数の基本構文
LAST_DAYは、指定した日付が属する月の最終日を返すOracle組み込み関数です。引数にDATE型またはTIMESTAMP型の値を渡すと、その月の末日をDATE型で返します。
| 項目 |
説明 |
| 引数 |
DATE型またはTIMESTAMP型の日付値 |
| 戻り値 |
引数の月の最終日(DATE型) |
| NULL |
引数がNULLの場合、NULLを返す |
| 時刻部分 |
引数の時刻部分がそのまま保持される |
最も基本的な使い方を見てみましょう。
SQL
-- 指定日付の月末を取得
SELECT LAST_DAY(TO_DATE('2025-06-15', 'YYYY-MM-DD')) AS month_end
FROM DUAL;
実行結果
MONTH_END
-----------
2025-06-30
6月15日を渡すと、6月の最終日である6月30日が返されます。月の何日目を渡しても、必ずその月の末日を返すのがLAST_DAYの特徴です。
当月末・前月末・翌月末の取得
実務では、現在日付を基準に当月末・前月末・翌月末を求めるケースが多くあります。LAST_DAYとADD_MONTHSを組み合わせることで簡単に取得できます。
当月末の取得
SQL
-- 当月末を取得
SELECT LAST_DAY(SYSDATE) AS this_month_end
FROM DUAL;
実行結果(実行日: 2025-03-15の場合)
THIS_MONTH_END
--------------
2025-03-31
前月末の取得
SQL
-- 前月末を取得
SELECT LAST_DAY(ADD_MONTHS(SYSDATE, -1)) AS prev_month_end
FROM DUAL;
実行結果
PREV_MONTH_END
--------------
2025-02-28
翌月末の取得
SQL
-- 翌月末を取得
SELECT LAST_DAY(ADD_MONTHS(SYSDATE, 1)) AS next_month_end
FROM DUAL;
実行結果
NEXT_MONTH_END
--------------
2025-04-30
まとめて取得
3つの月末をまとめて1回のSQLで取得することもできます。
SQL
-- 前月末・当月末・翌月末をまとめて取得
SELECT
LAST_DAY(ADD_MONTHS(SYSDATE, -1)) AS prev_month_end,
LAST_DAY(SYSDATE) AS this_month_end,
LAST_DAY(ADD_MONTHS(SYSDATE, 1)) AS next_month_end
FROM DUAL;
実行結果
PREV_MONTH_END THIS_MONTH_END NEXT_MONTH_END
-------------- -------------- --------------
2025-02-28 2025-03-31 2025-04-30
ポイント:ADD_MONTHSの第2引数に負の値を指定すると過去の月、正の値で未来の月を指定できます。例えば ADD_MONTHS(SYSDATE, -3) で3ヶ月前の月末を取得できます。
月初日の取得(TRUNC + ADD_MONTHS)
LAST_DAYは月末を取得する関数ですが、月初日(1日)を取得するにはいくつかの方法があります。
方法1: TRUNCを使う(推奨)
SQL
-- TRUNCで当月1日を取得
SELECT TRUNC(SYSDATE, 'MM') AS first_day
FROM DUAL;
実行結果
FIRST_DAY
-----------
2025-03-01
方法2: LAST_DAYから算出する
SQL
-- 前月末 + 1日 = 当月1日
SELECT LAST_DAY(ADD_MONTHS(SYSDATE, -1)) + 1 AS first_day
FROM DUAL;
実行結果
FIRST_DAY
-----------
2025-03-01
月初日と月末日をセットで取得
SQL
-- 当月の初日と末日をセットで取得
SELECT
TRUNC(SYSDATE, 'MM') AS first_day,
LAST_DAY(SYSDATE) AS last_day
FROM DUAL;
実行結果
FIRST_DAY LAST_DAY
----------- -----------
2025-03-01 2025-03-31
TRUNCの月切り捨てについて
TRUNC(date, 'MM') は日付を月の初日に切り捨てる
- 時刻部分もリセットされ 00:00:00 になる
TRUNC(date, 'YYYY') とすれば年初(1月1日)も取得可能
うるう年2月末の動作
LAST_DAY関数はうるう年を自動的に判定します。手動で28日・29日を判定する必要はありません。
SQL
-- うるう年 vs 平年の2月末比較
SELECT
LAST_DAY(TO_DATE('2024-02-01', 'YYYY-MM-DD')) AS leap_year_2024,
LAST_DAY(TO_DATE('2025-02-01', 'YYYY-MM-DD')) AS normal_year_2025,
LAST_DAY(TO_DATE('2028-02-15', 'YYYY-MM-DD')) AS leap_year_2028,
LAST_DAY(TO_DATE('2100-02-10', 'YYYY-MM-DD')) AS century_2100
FROM DUAL;
実行結果
LEAP_YEAR_2024 NORMAL_YEAR_2025 LEAP_YEAR_2028 CENTURY_2100
-------------- ---------------- -------------- ------------
2024-02-29 2025-02-28 2028-02-29 2100-02-28
| 年 |
うるう年? |
2月末日 |
理由 |
| 2024 |
Yes |
2月29日 |
4で割り切れる |
| 2025 |
No |
2月28日 |
4で割り切れない |
| 2028 |
Yes |
2月29日 |
4で割り切れる |
| 2100 |
No |
2月28日 |
100で割り切れるが400で割り切れない |
ポイント:LAST_DAY関数はうるう年のルール(4年に1回、ただし100年に1回はスキップ、400年に1回は適用)を完全に考慮します。手動で判定する必要はありません。
月末判定(今日が月末かどうか)
バッチ処理や月次処理で「今日が月末かどうか」を判定したいケースがあります。LAST_DAYとSYSDATEを比較することで簡単に実現できます。
基本的な月末判定
SQL
-- 今日が月末かどうか判定
SELECT
SYSDATE AS today,
LAST_DAY(SYSDATE) AS month_end,
CASE
WHEN TRUNC(SYSDATE) = LAST_DAY(TRUNC(SYSDATE))
THEN '月末です'
ELSE '月末ではありません'
END AS is_month_end
FROM DUAL;
実行結果(実行日: 2025-03-31の場合)
TODAY MONTH_END IS_MONTH_END
---------- ---------- ------------------
2025-03-31 2025-03-31 月末です
注意:SYSDATEには時刻情報が含まれるため、比較時はTRUNCで時刻部分を切り捨てる必要があります。TRUNCを忘れると、時刻のずれで一致しなくなる場合があります。
PL/SQLでの月末判定
PL/SQL
DECLARE
v_today DATE := TRUNC(SYSDATE);
BEGIN
IF v_today = LAST_DAY(v_today) THEN
DBMS_OUTPUT.PUT_LINE('今日は月末です。月次処理を実行します。');
-- 月次処理をここに記述
ELSE
DBMS_OUTPUT.PUT_LINE('今日は月末ではありません。');
END IF;
END;
/
月末までの残日数を取得
SQL
-- 月末までの残日数
SELECT
TRUNC(SYSDATE) AS today,
LAST_DAY(SYSDATE) AS month_end,
LAST_DAY(SYSDATE) - TRUNC(SYSDATE) AS remaining_days
FROM DUAL;
実行結果(実行日: 2025-03-15の場合)
TODAY MONTH_END REMAINING_DAYS
---------- ---------- --------------
2025-03-15 2025-03-31 16
月末のN営業日前を取得
経理・会計処理では「月末のN営業日前」を求める必要があることがあります。土日を除いた営業日ベースで計算する方法を紹介します。
月末の2営業日前を取得
SQL
-- 月末の2営業日前(土日を除く)
SELECT
LAST_DAY(SYSDATE) AS month_end,
CASE
-- 月末が月曜の場合: 2営業日前 = 前週木曜(-4日)
WHEN TO_CHAR(LAST_DAY(SYSDATE), 'DY', 'NLS_DATE_LANGUAGE=AMERICAN') = 'MON'
THEN LAST_DAY(SYSDATE) - 4
-- 月末が火曜の場合: 2営業日前 = 前週金曜(-4日)
WHEN TO_CHAR(LAST_DAY(SYSDATE), 'DY', 'NLS_DATE_LANGUAGE=AMERICAN') = 'TUE'
THEN LAST_DAY(SYSDATE) - 4
-- 月末が日曜の場合: 2営業日前 = 木曜(-3日)
WHEN TO_CHAR(LAST_DAY(SYSDATE), 'DY', 'NLS_DATE_LANGUAGE=AMERICAN') = 'SUN'
THEN LAST_DAY(SYSDATE) - 4
-- 月末が土曜の場合: 2営業日前 = 水曜(-3日)
WHEN TO_CHAR(LAST_DAY(SYSDATE), 'DY', 'NLS_DATE_LANGUAGE=AMERICAN') = 'SAT'
THEN LAST_DAY(SYSDATE) - 3
-- それ以外(水〜金): 単純に-2日
ELSE LAST_DAY(SYSDATE) - 2
END AS two_biz_days_before
FROM DUAL;
汎用的なN営業日前関数
繰り返し使う場合は、ファンクションとして定義すると便利です。
PL/SQL
CREATE OR REPLACE FUNCTION get_biz_days_before_month_end(
p_date IN DATE,
p_days IN NUMBER
) RETURN DATE IS
v_target DATE := LAST_DAY(p_date);
v_count NUMBER := 0;
BEGIN
WHILE v_count < p_days LOOP
v_target := v_target - 1;
-- 土日(1=日曜, 7=土曜)をスキップ
IF TO_CHAR(v_target, 'D') NOT IN ('1', '7') THEN
v_count := v_count + 1;
END IF;
END LOOP;
RETURN v_target;
END;
/
SQL
-- 使用例: 月末の3営業日前
SELECT get_biz_days_before_month_end(SYSDATE, 3) FROM DUAL;
注意:TO_CHAR(date, 'D') の値はNLS_TERRITORYの設定に依存します。日本(JAPAN)では日曜=1、土曜=7ですが、環境によって異なる場合があります。安全のため TO_CHAR(date, 'DY', 'NLS_DATE_LANGUAGE=AMERICAN') で曜日名を判定する方法も検討してください。
ADD_MONTHSとの組み合わせ
ADD_MONTHSとLAST_DAYを組み合わせると、任意の月の月末を柔軟に取得できます。
過去・未来の月末を一括取得
SQL
-- 過去6ヶ月〜未来6ヶ月の月末一覧
SELECT
TO_CHAR(ADD_MONTHS(SYSDATE, LEVEL - 7), 'YYYY-MM') AS year_month,
LAST_DAY(ADD_MONTHS(SYSDATE, LEVEL - 7)) AS month_end
FROM DUAL
CONNECT BY LEVEL <= 13;
実行結果
YEAR_MONTH MONTH_END
---------- -----------
2024-09 2024-09-30
2024-10 2024-10-31
2024-11 2024-11-30
2024-12 2024-12-31
2025-01 2025-01-31
2025-02 2025-02-28
2025-03 2025-03-31
2025-04 2025-04-30
2025-05 2025-05-31
2025-06 2025-06-30
2025-07 2025-07-31
2025-08 2025-08-31
2025-09 2025-09-30
各月の日数を取得
SQL
-- LAST_DAYで月の日数を計算
SELECT
TO_CHAR(SYSDATE, 'YYYY-MM') AS year_month,
TO_NUMBER(TO_CHAR(LAST_DAY(SYSDATE), 'DD')) AS days_in_month
FROM DUAL;
実行結果(2025年3月の場合)
YEAR_MONTH DAYS_IN_MONTH
---------- -------------
2025-03 31
ADD_MONTHSの月末特有の挙動
ADD_MONTHSには月末日に対する特殊な挙動があります。この特性を理解しておくことが重要です。
SQL
-- ADD_MONTHSの月末挙動
SELECT
TO_DATE('2025-01-31', 'YYYY-MM-DD') AS base_date,
ADD_MONTHS(TO_DATE('2025-01-31', 'YYYY-MM-DD'), 1) AS plus_1_month,
ADD_MONTHS(TO_DATE('2025-01-31', 'YYYY-MM-DD'), 2) AS plus_2_months
FROM DUAL;
実行結果
BASE_DATE PLUS_1_MONTH PLUS_2_MONTHS
---------- ------------ -------------
2025-01-31 2025-02-28 2025-03-31
ADD_MONTHSの月末ルール
- 元の日付が月末の場合、結果もその月の月末になる
- 例: 1/31 + 1ヶ月 = 2/28(2月の月末)
- 元の日付が月末でない場合、日は保持される(例: 1/15 + 1ヶ月 = 2/15)
- この挙動によりLAST_DAYなしでも月末が維持される場合がある
WHERE句での月末データ抽出
テーブルから月末日のデータだけを抽出したり、月末を基準にした期間絞り込みを行うパターンです。
月末日のレコードを抽出
SQL
-- 日付が月末のレコードのみ抽出
SELECT *
FROM sales_data
WHERE TRUNC(sale_date) = LAST_DAY(TRUNC(sale_date));
当月の初日〜末日で絞り込み
SQL
-- 当月のデータを取得
SELECT *
FROM orders
WHERE order_date BETWEEN
TRUNC(SYSDATE, 'MM')
AND LAST_DAY(SYSDATE);
前月分データの抽出
SQL
-- 前月の1日〜末日で絞り込み
SELECT *
FROM orders
WHERE order_date BETWEEN
TRUNC(ADD_MONTHS(SYSDATE, -1), 'MM')
AND LAST_DAY(ADD_MONTHS(SYSDATE, -1));
月別集計で月末日を表示
SQL
-- 月別売上集計と月末日表示
SELECT
TO_CHAR(sale_date, 'YYYY-MM') AS year_month,
LAST_DAY(MIN(sale_date)) AS month_end,
COUNT(*) AS record_count,
SUM(amount) AS total_amount
FROM sales_data
GROUP BY TO_CHAR(sale_date, 'YYYY-MM')
ORDER BY year_month;
注意:BETWEENで日付範囲を指定する場合、日付列に時刻情報が含まれていると月末23:59:59のデータが漏れる場合があります。時刻付きの場合は LAST_DAY(SYSDATE) + 1 - 1/86400 や < LAST_DAY(SYSDATE) + 1 のように指定しましょう。
他RDBMSとの比較
月末取得の方法はRDBMSによって異なります。Oracle以外のデータベースでの書き方を比較します。
| RDBMS |
関数 |
使用例 |
| Oracle |
LAST_DAY(date) |
LAST_DAY(SYSDATE) |
| MySQL |
LAST_DAY(date) |
LAST_DAY(CURDATE()) |
| PostgreSQL |
直接的な関数なし |
(date_trunc('month', CURRENT_DATE) + interval '1 month - 1 day')::date |
| SQL Server |
EOMONTH(date) |
EOMONTH(GETDATE()) |
| SQLite |
直接的な関数なし |
date(date, 'start of month', '+1 month', '-1 day') |
MySQL LAST_DAY
MySQLにもOracle同名のLAST_DAY関数があり、使い方はほぼ同じです。
MySQL
-- MySQL: 月末を取得
SELECT LAST_DAY(CURDATE()) AS month_end;
-- MySQL: 前月末を取得
SELECT LAST_DAY(CURDATE() - INTERVAL 1 MONTH) AS prev_month_end;
PostgreSQL
PostgreSQLにはLAST_DAY関数がないため、date_truncと日付演算で代用します。
PostgreSQL
-- PostgreSQL: 月末を取得
SELECT
(date_trunc('month', CURRENT_DATE)
+ INTERVAL '1 month'
- INTERVAL '1 day')::date AS month_end;
SQL Server EOMONTH
SQL Server 2012以降ではEOMONTH関数が使えます。第2引数でオフセット月数を指定できるのが便利です。
SQL Server
-- SQL Server: 月末を取得
SELECT EOMONTH(GETDATE()) AS month_end;
-- SQL Server: 前月末(第2引数でオフセット)
SELECT EOMONTH(GETDATE(), -1) AS prev_month_end;
ポイント:SQL ServerのEOMONTHは第2引数でオフセットを指定できる点がOracleのLAST_DAYと異なります。OracleではADD_MONTHSと組み合わせる必要がある処理を、SQL Serverでは1関数で完結できます。
よくあるエラーと対処法
LAST_DAY関数を使う際に発生しやすいエラーと、その対処法をまとめます。
| エラー |
原因 |
対処法 |
ORA-01841 |
文字列を日付に変換できない |
TO_DATEで正しいフォーマットを指定 |
ORA-01843 |
無効な月が指定された |
月の値が1〜12であることを確認 |
ORA-01858 |
数値が必要な位置に非数値文字 |
日付文字列のフォーマット確認 |
| NULL返却 |
引数がNULL |
NVLでデフォルト値を設定 |
| 時刻ずれ |
比較時に時刻部分が不一致 |
TRUNCで時刻を切り捨てて比較 |
エラー例1: 文字列を直接渡す
NGパターン
-- NLS_DATE_FORMATに依存するNG例
SELECT LAST_DAY('2025-03-15') FROM DUAL;
-- → ORA-01841 になる可能性あり
OKパターン
-- TO_DATEで明示的に変換するOK例
SELECT LAST_DAY(TO_DATE('2025-03-15', 'YYYY-MM-DD')) FROM DUAL;
エラー例2: NULL引数の処理
SQL
-- NULLの場合にデフォルト値を設定
SELECT
LAST_DAY(NVL(target_date, SYSDATE)) AS month_end
FROM some_table;
エラー例3: 月末の時刻問題
SQL
-- NG: 時刻情報があると不一致になる場合
SELECT * FROM orders
WHERE order_date = LAST_DAY(SYSDATE); -- 時刻が一致しない!
-- OK: TRUNCで日付部分のみ比較
SELECT * FROM orders
WHERE TRUNC(order_date) = LAST_DAY(TRUNC(SYSDATE));
実務パターン(月次締め処理等)
実際の業務で頻繁に使われるLAST_DAYの活用パターンをまとめます。
月次締め処理の日付設定
PL/SQL
-- 月次締め処理: 前月分データの集計
DECLARE
v_start_date DATE; -- 前月1日
v_end_date DATE; -- 前月末日
v_close_ym VARCHAR2(7); -- 締め年月
BEGIN
-- 前月の期間を算出
v_start_date := TRUNC(ADD_MONTHS(SYSDATE, -1), 'MM');
v_end_date := LAST_DAY(ADD_MONTHS(SYSDATE, -1));
v_close_ym := TO_CHAR(v_start_date, 'YYYY-MM');
-- 月次集計テーブルにINSERT
INSERT INTO monthly_summary (
close_ym, start_date, end_date, total_sales, record_count
)
SELECT
v_close_ym,
v_start_date,
v_end_date,
SUM(amount),
COUNT(*)
FROM sales_data
WHERE sale_date BETWEEN v_start_date AND v_end_date;
COMMIT;
DBMS_OUTPUT.PUT_LINE(v_close_ym || ' の月次締め完了');
END;
/
支払期限の算出(月末締め翌月末払い)
SQL
-- 月末締め翌月末払い: 支払期限を算出
SELECT
invoice_no,
invoice_date,
LAST_DAY(invoice_date) AS closing_date,
LAST_DAY(ADD_MONTHS(invoice_date, 1)) AS payment_due
FROM invoices;
実行結果
INVOICE_NO INVOICE_DATE CLOSING_DATE PAYMENT_DUE
---------- ------------ ------------ -----------
INV-001 2025-03-10 2025-03-31 2025-04-30
INV-002 2025-03-25 2025-03-31 2025-04-30
INV-003 2025-04-05 2025-04-30 2025-05-31
月末締め翌月25日払い
SQL
-- 月末締め翌月25日払い
SELECT
invoice_no,
invoice_date,
LAST_DAY(invoice_date) AS closing_date,
TRUNC(ADD_MONTHS(invoice_date, 1), 'MM') + 24 AS payment_due
FROM invoices;
四半期末の取得
SQL
-- 四半期末(3月末, 6月末, 9月末, 12月末)を取得
SELECT
SYSDATE AS today,
LAST_DAY(TRUNC(SYSDATE, 'Q')
+ INTERVAL '2' MONTH) AS quarter_end
FROM DUAL;
実行結果(2025-03-15の場合)
TODAY QUARTER_END
---------- -----------
2025-03-15 2025-03-31
月末スナップショットテーブルへのINSERT
SQL
-- 月末時点の残高スナップショット
INSERT INTO balance_snapshot (
snapshot_date, account_id, balance
)
SELECT
LAST_DAY(SYSDATE),
account_id,
current_balance
FROM accounts
WHERE status = 'ACTIVE';
まとめ
OracleのLAST_DAY関数は、月末日の取得に特化したシンプルかつ強力な関数です。この記事の要点を振り返りましょう。
| 用途 |
SQL |
| 当月末 |
LAST_DAY(SYSDATE) |
| 前月末 |
LAST_DAY(ADD_MONTHS(SYSDATE, -1)) |
| 翌月末 |
LAST_DAY(ADD_MONTHS(SYSDATE, 1)) |
| 月初日 |
TRUNC(SYSDATE, 'MM') |
| 月の日数 |
TO_NUMBER(TO_CHAR(LAST_DAY(SYSDATE), 'DD')) |
| 月末判定 |
TRUNC(SYSDATE) = LAST_DAY(TRUNC(SYSDATE)) |
| 月末までの残日数 |
LAST_DAY(SYSDATE) - TRUNC(SYSDATE) |
| 四半期末 |
LAST_DAY(TRUNC(SYSDATE, 'Q') + INTERVAL '2' MONTH) |
実務で押さえておくべきポイント
- 日付比較時はTRUNCで時刻部分を除去してから比較する
- 文字列を渡す場合は必ずTO_DATEで明示的に変換する
- LAST_DAYはうるう年を自動判定するため手動判定は不要
- ADD_MONTHSと組み合わせて前月末・翌月末を取得する
- BETWEENで時刻付き日付を扱う場合は範囲漏れに注意する
- 他RDBMSへの移行時は関数名の違いに注意する(PostgreSQLにはLAST_DAYがない)