Oracle の TRUNC 関数は日付の時刻部分や月・年の単位で切り捨てができる強力な関数です。TRUNC(SYSDATE) で今日の 00:00:00、TRUNC(date, 'MM') で月初 1 日など、日付集計・期間絞り込みで必須のテクニックをすべて解説します。
- TRUNC で日付の時刻部分(時・分・秒)を切り捨てる方法
- TRUNC(SYSDATE) で今日の 00:00:00 を取得する方法
- 月初・年初・四半期初・週初の日付を求める方法
- 時間単位・分単位への切り捨て
- 月別・週別・時間帯別の集計クエリパターン
- TIMESTAMP 型への適用と注意点
TRUNC 関数の構文(日付版)
TRUNC(date [, fmt])
| 引数 | 型 | 説明 |
|---|---|---|
date |
DATE / TIMESTAMP | 切り捨て対象の日付・タイムスタンプ |
fmt |
VARCHAR2(省略可) | 切り捨て単位を指定する書式文字列。省略時は 'DD'(日単位=時刻を 00:00:00 に) |
戻り値は常に DATE 型です。TIMESTAMP を渡した場合も DATE に変換されます。
TRUNC(1.567, 2) のように数値にも使えますが、日付版は第 2 引数が 書式文字列('MM' など)になります。構文は同じですが意味が異なるため、混在しないよう注意してください。
基本的な使い方
時刻を切り捨てる(今日の 00:00:00)
TRUNC(SYSDATE) は最もよく使われるパターンです。時・分・秒を 00:00:00 に切り捨て、純粋な「日付」を返します。
-- SYSDATE が 2024-07-17 14:35:22 の場合 SELECT SYSDATE FROM DUAL; -- 2024-07-17 14:35:22 SELECT TRUNC(SYSDATE) FROM DUAL; -- 2024-07-17 00:00:00 SELECT TRUNC(SYSDATE, 'DD') FROM DUAL; -- 2024-07-17 00:00:00 ← DD は省略時と同じ -- 任意の日付変数にも同様に使える SELECT TRUNC(order_date) FROM orders; -- 時刻なしの受注日付 SELECT TRUNC(created_at) FROM users; -- 時刻なしの登録日付
Oracle の DATE 型は年月日だけでなく時・分・秒も持っています。
'2024-07-17' と見えても内部は 2024-07-17 00:00:00 です。order_date = SYSDATE のような比較では時刻の差で一致しないケースがあります。日付だけで比較・集計するときは必ず TRUNC を使いましょう。
月初を求める(TRUNC + MM)
-- SYSDATE が 2024-07-17 の場合 SELECT TRUNC(SYSDATE, 'MM') FROM DUAL; -- 2024-07-01 00:00:00 SELECT TRUNC(SYSDATE, 'MONTH') FROM DUAL; -- 2024-07-01 00:00:00 ← 同義 -- 先月初・翌月初も計算できる SELECT TRUNC(ADD_MONTHS(SYSDATE, -1), 'MM') FROM DUAL; -- 2024-06-01(先月初) SELECT ADD_MONTHS(TRUNC(SYSDATE, 'MM'), 1) FROM DUAL; -- 2024-08-01(翌月初)
年初を求める(TRUNC + YYYY)
-- SYSDATE が 2024-07-17 の場合 SELECT TRUNC(SYSDATE, 'YYYY') FROM DUAL; -- 2024-01-01 00:00:00 SELECT TRUNC(SYSDATE, 'YEAR') FROM DUAL; -- 2024-01-01 00:00:00 ← 同義 SELECT TRUNC(SYSDATE, 'YY') FROM DUAL; -- 2024-01-01 00:00:00 ← 同義 -- 去年初・来年初 SELECT ADD_MONTHS(TRUNC(SYSDATE, 'YYYY'), -12) FROM DUAL; -- 2023-01-01(去年初) SELECT ADD_MONTHS(TRUNC(SYSDATE, 'YYYY'), 12) FROM DUAL; -- 2025-01-01(来年初)
四半期初を求める(TRUNC + Q)
-- SYSDATE が 2024-07-17 の場合(Q3: 7月〜9月) SELECT TRUNC(SYSDATE, 'Q') FROM DUAL; -- 2024-07-01 00:00:00(Q3 の開始) -- 月ごとの四半期開始日の例 -- 1月→1/1, 2月→1/1, 3月→1/1(Q1) -- 4月→4/1, 5月→4/1, 6月→4/1(Q2) -- 7月→7/1, 8月→7/1, 9月→7/1(Q3) -- 10月→10/1, 11月→10/1, 12月→10/1(Q4)
週の開始日を求める(TRUNC + DAY / WW)
-- SYSDATE が 2024-07-17(水曜日)の場合 SELECT TRUNC(SYSDATE, 'DAY') FROM DUAL; -- 2024-07-14 00:00:00(直近の日曜日) SELECT TRUNC(SYSDATE, 'DY') FROM DUAL; -- 2024-07-14 00:00:00 ← 同義 -- WW:年の最初の日と同じ曜日に揃える SELECT TRUNC(SYSDATE, 'WW') FROM DUAL; -- 年の第 1 日(1/1)と同じ曜日の週頭 -- ※ DAY の週開始曜日は NLS_TERRITORY に依存(日本・米国はデフォルト日曜日)
時間単位・分単位に切り捨てる
-- SYSDATE が 2024-07-17 14:35:22 の場合 SELECT TRUNC(SYSDATE, 'HH') FROM DUAL; -- 2024-07-17 14:00:00(時間単位) SELECT TRUNC(SYSDATE, 'HH24') FROM DUAL; -- 2024-07-17 14:00:00 ← 同義 SELECT TRUNC(SYSDATE, 'HH12') FROM DUAL; -- 2024-07-17 14:00:00 ← 同義 SELECT TRUNC(SYSDATE, 'MI') FROM DUAL; -- 2024-07-17 14:35:00(分単位、秒を切り捨て)
書式文字列一覧
| 書式文字列 | 切り捨て単位 | 例(入力: 2024-07-17 14:35:22) |
|---|---|---|
YYYY / YEAR / YY |
年(1月1日に) | 2024-01-01 00:00:00 |
Q |
四半期(四半期の第1日に) | 2024-07-01 00:00:00 |
MM / MONTH / MON |
月(1日に) | 2024-07-01 00:00:00 |
WW |
週(年の第1日と同じ曜日) | NLS依存 |
W |
週(月の第1日と同じ曜日) | NLS依存 |
DAY / DY / D |
週(週開始曜日に) | 2024-07-14 00:00:00(日曜日) |
DD / J(省略時も同じ) |
日(00:00:00 に) | 2024-07-17 00:00:00 |
HH / HH12 / HH24 |
時間(分・秒を 00 に) | 2024-07-17 14:00:00 |
MI |
分(秒を 00 に) | 2024-07-17 14:35:00 |
TRUNC と ROUND の違い(日付)
日付の丸めには TRUNC(切り捨て)と ROUND(四捨五入)があります。月初を求める場合の違いを確認してください。
-- 2024-07-17 の場合 SELECT TRUNC(DATE '2024-07-17', 'MM') FROM DUAL; -- 2024-07-01(常に当月 1 日) SELECT ROUND(DATE '2024-07-17', 'MM') FROM DUAL; -- 2024-07-01(16 日未満 → 当月) -- 2024-07-16 の場合(ROUND の境界) SELECT TRUNC(DATE '2024-07-16', 'MM') FROM DUAL; -- 2024-07-01(TRUNC は常に当月) SELECT ROUND(DATE '2024-07-16', 'MM') FROM DUAL; -- 2024-08-01(16 日以降 → 翌月) -- 集計クエリでは TRUNC が安全 -- ROUND では月初を超えて翌月に飛ぶケースがあるため、期間絞り込みには TRUNC を使う
ROUND(date, 'MM') は月の中間日(16 日)を境界に翌月に丸まることがあります。「当月 1 日」を確実に取得したい場合は必ず TRUNC(date, 'MM') を使ってください。
実務パターン集
今日・今月・今年のデータを集計する
-- 今日のデータ(時刻を無視して日付のみで絞り込み) SELECT COUNT(*), SUM(amount) FROM orders WHERE TRUNC(order_date) = TRUNC(SYSDATE); -- 今月のデータ(月初〜翌月初の範囲) SELECT COUNT(*), SUM(amount) FROM orders WHERE order_date >= TRUNC(SYSDATE, 'MM') AND order_date < ADD_MONTHS(TRUNC(SYSDATE, 'MM'), 1); -- 今年のデータ SELECT COUNT(*), SUM(amount) FROM orders WHERE order_date >= TRUNC(SYSDATE, 'YYYY') AND order_date < ADD_MONTHS(TRUNC(SYSDATE, 'YYYY'), 12);
BETWEEN TRUNC(SYSDATE, 'MM') AND ADD_MONTHS(...) - 1 という書き方では月末の 23:59:59 のデータが漏れる場合があります。>= 月初 AND < 翌月初 の形式が最も安全です。
月別集計レポート(GROUP BY)
-- 月別の売上集計 SELECT TRUNC(order_date, 'MM') AS month_start, TO_CHAR(order_date, 'YYYY-MM') AS month_label, COUNT(*) AS order_count, SUM(amount) AS total_amount FROM orders WHERE order_date >= ADD_MONTHS(TRUNC(SYSDATE, 'YYYY'), -12) GROUP BY TRUNC(order_date, 'MM'), TO_CHAR(order_date, 'YYYY-MM') ORDER BY month_start; -- 年別の集計 SELECT TO_CHAR(TRUNC(order_date, 'YYYY'), 'YYYY') AS year_label, COUNT(*) AS order_count, SUM(amount) AS total_amount FROM orders GROUP BY TRUNC(order_date, 'YYYY') ORDER BY TRUNC(order_date, 'YYYY');
時間帯別の集計
-- 1 時間単位で集計 SELECT TRUNC(order_time, 'HH') AS hour_block, TO_CHAR(TRUNC(order_time, 'HH'), 'HH24') AS hour_label, COUNT(*) AS order_count FROM orders WHERE TRUNC(order_time) = TRUNC(SYSDATE) GROUP BY TRUNC(order_time, 'HH') ORDER BY hour_block; -- 30 分単位での集計(TRUNC で 30 分ブロックに丸める) SELECT TRUNC(order_time, 'HH') + (FLOOR(TO_NUMBER(TO_CHAR(order_time, 'MI')) / 30) / 48) AS block_30min, COUNT(*) FROM orders WHERE TRUNC(order_time) = TRUNC(SYSDATE) GROUP BY TRUNC(order_time, 'HH') + (FLOOR(TO_NUMBER(TO_CHAR(order_time, 'MI')) / 30) / 48) ORDER BY block_30min;
N 日前・N ヶ月前との比較
-- 過去 7 日間のデータ(今日を含む)
SELECT *
FROM access_log
WHERE access_time >= TRUNC(SYSDATE) - 6
AND access_time < TRUNC(SYSDATE) + 1;
-- 先月のデータ
SELECT *
FROM sales
WHERE sale_date >= ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -1)
AND sale_date < TRUNC(SYSDATE, 'MM');
-- 先月同日(月初からの経過日数を揃える)
SELECT
TRUNC(SYSDATE) - TRUNC(SYSDATE, 'MM') AS days_since_month_start,
ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -1) AS last_month_start,
ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -1)
+ (TRUNC(SYSDATE) - TRUNC(SYSDATE, 'MM')) AS same_day_last_month
FROM DUAL;
TIMESTAMP 型への適用
TRUNC は TIMESTAMP 型にも適用できますが、戻り値は DATE 型に変換されます。ナノ秒精度が失われる点に注意してください。
-- TIMESTAMP に TRUNC を適用 SELECT TRUNC(SYSTIMESTAMP) FROM DUAL; -- DATE 型で今日の 00:00:00 SELECT TRUNC(SYSTIMESTAMP, 'HH') FROM DUAL; -- DATE 型で今時の 00 分 00 秒 -- TIMESTAMP のまま時刻を切り捨てたい場合は CAST を使う SELECT CAST(TRUNC(SYSTIMESTAMP) AS TIMESTAMP) FROM DUAL; -- TIMESTAMP に再変換 -- TIMESTAMP_TZ (タイムゾーン付き) への注意 -- TRUNC(SYSTIMESTAMP AT TIME ZONE 'Asia/Tokyo', 'DD') はエラーになる場合がある -- → CAST で DATE 経由にするかセッションの TIMEZONE を設定してから使う
created_at が TIMESTAMP 型の場合、日付単位の絞り込みは次のように書けます。WHERE TRUNC(created_at) = TRUNC(SYSDATE)TRUNC の戻り値が DATE 型になるため、双方 DATE に揃えられ正しく比較できます。
よくある質問(FAQ)
はい、全く同じです。TRUNC(date) の fmt を省略した場合のデフォルトは 'DD'(日単位)です。どちらも時・分・秒を 00:00:00 に切り捨て、日付部分だけを返します。
可読性のために TRUNC(SYSDATE)(時刻カット)と覚えておくのがシンプルです。
WHERE TRUNC(order_date) = TRUNC(SYSDATE) のように 列側に TRUNC を使うと通常のインデックスは使われません(列値ではなく関数結果での評価になるため)。
パフォーマンスが重要な場合は次のいずれかを検討してください。
-- 方法 1: 範囲条件に書き換える(インデックスが使われやすい) WHERE order_date >= TRUNC(SYSDATE) AND order_date < TRUNC(SYSDATE) + 1 -- 方法 2: ファンクションベースインデックスを作成する CREATE INDEX idx_order_date_trunc ON orders (TRUNC(order_date)); -- この後 WHERE TRUNC(order_date) = TRUNC(SYSDATE) でインデックスが使われる
TRUNC 単体では月末を直接求められません。LAST_DAY 関数か、「翌月 1 日の 1 秒前」で計算します。
-- 月末の日付(時刻なし) SELECT LAST_DAY(TRUNC(SYSDATE, 'MM')) FROM DUAL; -- 2024-07-31 00:00:00 -- 月末の 23:59:59(その月の最後の秒) SELECT ADD_MONTHS(TRUNC(SYSDATE, 'MM'), 1) - (1/86400) FROM DUAL; -- 2024-07-31 23:59:59
Oracle の TRUNC(date, 'DAY') はデフォルトで日曜日を週の開始日とします。月曜日を週頭にしたい場合は手動で計算します。
-- 月曜日始まりの週頭を求める(日曜日以外の曜日では機能する)
SELECT TRUNC(SYSDATE, 'DAY') + 1 FROM DUAL; -- 直前の日曜日 + 1 日 = 月曜日
-- 注意: SYSDATE が日曜日の場合は TRUNC = 当日(日曜日)なので +1 は翌週月曜日になる
-- より厳密な計算(何曜日でも当週月曜日を返す)
SELECT TRUNC(SYSDATE) - MOD(TRUNC(SYSDATE) - TO_DATE('1970-01-05', 'YYYY-MM-DD'), 7) FROM DUAL;
-- 1970-01-05 は月曜日 → この基準日からの日数の余りで補正する
Oracle の DATE 型同士の減算は差分を日数(数値)で返します。TRUNC で時刻を統一してから引くと、ちょうどの日数差が得られます。
-- 今日から指定日までの日数
SELECT TRUNC(SYSDATE) - TRUNC(TO_DATE('2024-01-01', 'YYYY-MM-DD')) FROM DUAL; -- 198(日)
-- TRUNC しないと時刻の差で小数が出ることがある
SELECT SYSDATE - TO_DATE('2024-01-01', 'YYYY-MM-DD') FROM DUAL; -- 198.6... のような値
-- 整数で日数差を確実に求めるには TRUNC を使う
SELECT TRUNC(SYSDATE) - TRUNC(start_date) AS days_elapsed FROM tasks;
まとめ
| やりたいこと | 構文 | 結果例(2024-07-17 14:35:22) |
|---|---|---|
| 時刻を切り捨て(今日 00:00:00) | TRUNC(SYSDATE) |
2024-07-17 00:00:00 |
| 月初(当月 1 日) | TRUNC(date, 'MM') |
2024-07-01 00:00:00 |
| 年初(当年 1/1) | TRUNC(date, 'YYYY') |
2024-01-01 00:00:00 |
| 四半期初 | TRUNC(date, 'Q') |
2024-07-01 00:00:00 |
| 週の開始日(日曜日) | TRUNC(date, 'DAY') |
2024-07-14 00:00:00 |
| 時間単位(分・秒をカット) | TRUNC(date, 'HH') |
2024-07-17 14:00:00 |
| 分単位(秒をカット) | TRUNC(date, 'MI') |
2024-07-17 14:35:00 |
| 今月のデータを絞り込む | >= TRUNC(SYSDATE,'MM') AND < ADD_MONTHS(...,1) |
— |
Oracle の TRUNC 関数は日付の切り捨てに欠かせないツールです。TRUNC(SYSDATE) で時刻カット、TRUNC(date, 'MM') で月初取得の 2 つを押さえるだけで、日常的な集計クエリの大半に対応できます。期間絞り込みは BETWEEN より >= AND < 形式を使い、インデックス効率を意識した書き方を心がけてください。

