SQLで月初・月末を取得する方法まとめ(主要DB別の書き方と注意点)
この記事でわかること(導入)
この記事では、SQLで「月初」と「月末」を求める基本の考え方と、Oracle/SQL Server/MySQL/PostgreSQLでの代表的な書き方をまとめます。
月次集計や請求締めなどでつまずきやすいのは「月末の扱い」と「WHERE句の境界条件」なので、結論として安全な抽出条件(`>= 月初 AND < 翌月月初`)を軸に整理します。
DBごとに関数名や戻り型が違っても、境界の作り方を統一すれば、移植や読み替えがしやすくなります。
以降のSQL例では、説明を揃えるために「対象日」を `2026-04-15` として考えます。
サンプルの「orders」テーブルは、日時カラムが `order_dt` である前提で記載します。
SQLで日付の月初・月末を取得する基本
月初・月末の算出はDBごとに関数が違っても、考え方は「その月の先頭(1日)」「その月の最後(最終日)」「抽出は境界で区切る」の3点に集約できます。
特に実務で重要なのは「月末の時刻」をどう扱うかで、日時型(時刻を持つ型)を相手にすると `2026-04-30 23:59:59` のように“秒を固定”した瞬間に取りこぼしが起きます。
そのため、集計対象を月単位で切るときは、月末そのものを作るよりも「翌月月初を作り、そこより手前(未満)で切る」ほうが安全です。
もうひとつの大事なポイントは「カラムに関数をかけない」形に寄せることで、インデックスが効きやすくなります。
月初・翌月月初という境界値を先に作っておき、WHERE句ではカラムをそのまま比較する設計を目指します。
「月初」「月末」の定義(抽出条件では境界が重要)
「月初」は多くの場合「その月の1日 00:00:00」を指し、日付型なら単に「その月の1日」です。
「月末」は日付としては「その月の最終日」ですが、日時型のデータ抽出では「月末の最後の瞬間」を作ろうとすると精度(ミリ秒やナノ秒)でズレます。
たとえばログや取引日時が `2026-04-30 23:59:59.900` のように小数秒を持つと、`23:59:59` までしか含めない条件で漏れてしまいます。
逆に「月末の最後の瞬間」を小数秒まで含めようとすると、DBや型ごとに“最小単位”が変わり、SQLが読みにくくなります。
だからこそ、抽出条件の設計では「月末を含む」より「翌月月初未満」を採用すると、型や精度が変わっても結果が安定します。
月末は「翌月月初未満」が安全な理由
月末までを含める条件を頑張って作ると、データ型がDATEかTIMESTAMPかで“最後”の定義が変わり、DB設定やアプリの保存形式によって件数が揺れます。
一方で「`>= 月初` かつ `< 翌月月初`」は、境界が必ず存在し、どの精度の日時でも“次の月に入った瞬間”で明確に区切れます。
さらに、`BETWEEN 月初 AND 月末` のような「終端を含む」条件だと、月末の作り方を間違えたときに静かに欠損が起きるので、保守面でも危険です。
また、日次バッチや再集計で同じ条件を何度も使う場合、境界未満の条件は再現性が高く、運用トラブルが減ります。
そのため、月次抽出の推奨形は次のように覚えると便利です。
| SQL |
|---|
| — 推奨(概念): |
| WHERE dt >= 月初 |
| AND dt < 翌月月初 |
月末日付が必要な場面(表示や締め日の出力など)と、抽出条件が必要な場面を分けて考えると、SQLが整理しやすくなります。
文字列ではなく日付型で扱う(暗黙変換を避ける)
日付を文字列(`’2026-04-01’` のようなVARCHAR)として比較できる場面もありますが、暗黙変換が入ると意図しない比較や性能劣化が起きます。
特にWHERE句でカラム側に関数をかけたり、文字列化して比較すると、インデックスが効きにくくなり、月次集計のような重い処理で差が出ます。
さらに、文字列の形式(ゼロ埋めやロケール)に依存すると、環境差でバグが混入しやすくなります。
まず「カラムは日付型として保持し、条件値も日付型として作る」ことを基本にすると、DB差があっても安定します。
Oracleで月初・月末を取得する方法
Oracleは月初の切り捨てに `TRUNC(…, ‘MM’)`、月末に `LAST_DAY` があり、算出はシンプルです。
ただし、抽出の安全性を高めるには「月末を作る」だけで満足せず「翌月月初未満」の条件もセットで覚えるのがおすすめです。
以下では、対象日を `DATE` として扱う例を中心に書きます。
また、カラムが `TIMESTAMP` の場合でも境界未満の考え方は同じです。
Oracleで月初を取得するSQL(単体)
月初は `TRUNC(対象日, ‘MM’)` で、その月の1日に切り捨てられます。
| SQL |
|---|
| — 対象日: DATE ‘2026-04-15’ |
| SELECT TRUNC(DATE ‘2026-04-15’, ‘MM’) AS month_start |
| FROM dual; |
カラムに対して使う場合も同じで、`TRUNC(order_dt, ‘MM’)` のように書けます。
ただし、WHERE句でカラム側に `TRUNC` をかけるとインデックスが使われにくいことがあるので、抽出条件は「境界の値を作って比較する」形を優先します。
境界値だけを作る用途なら、`TRUNC(:target_date,’MM’)` のようにバインド変数を使うと再利用がしやすくなります。
Oracleで月末を取得するSQL(単体)
月末の日付は `LAST_DAY(対象日)` で求められます。
| SQL |
|---|
| SELECT LAST_DAY(DATE ‘2026-04-15’) AS month_end |
| FROM dual; |
この結果は日付としての最終日なので、日時型の抽出で「その日の最終時刻」を作る用途には向きません。
月末日の表示が必要ならこの値を使い、抽出は次の「翌月月初未満」で切ると役割分担が明確になります。
Oracleで月次抽出に使うWHERE条件(推奨)
月初は `TRUNC(対象日, ‘MM’)`、翌月月初は `ADD_MONTHS(月初, 1)` で作れます。
| SQL |
|---|
| — 対象月: 2026-04 |
| — 月初: 2026-04-01 |
| — 翌月月初: 2026-05-01 |
| SELECT * |
| FROM orders |
| WHERE order_dt >= TRUNC(DATE ‘2026-04-15’, ‘MM’) |
| AND order_dt < ADD_MONTHS(TRUNC(DATE ‘2026-04-15’, ‘MM’), 1); |
この形なら、`order_dt` がDATEでもTIMESTAMPでも「5月に入った瞬間」で確実に切れます。
対象月をパラメータで受ける場合は、月初を `DATE ‘2026-04-01’` のように直接渡す設計にすると、SQLがさらに読みやすくなります。
月初がすでに渡せるなら、月初の算出を省略できるため、実行計画も安定しやすくなります。
SQL Serverで月初・月末を取得する方法
SQL Serverは月末に `EOMONTH` があり、月初は「その月の1日を作る」方式で揃えると理解しやすいです。
また、日時型は `datetime` と `datetime2` で精度が違うので、終端を含む条件より「翌月月初未満」を強くおすすめします。
以下では `date` と `datetime2` を想定しつつ、読みやすい定番形に絞って紹介します。
アプリ側で対象月を受け取る場合は、境界となる `@start` と `@next` を先に作るとSQLが整理できます。
SQL Serverで月初を取得するSQL(単体)
月初は `DATEFROMPARTS(年, 月, 1)` で作るのがわかりやすいです。
| SQL |
|---|
| DECLARE @d date = ‘2026-04-15’; |
| SELECT DATEFROMPARTS(YEAR(@d), MONTH(@d), 1) AS month_start; |
月初を求める別解もありますが、可読性と説明の揃えやすさを優先すると、この形が安心です。
もし入力がdatetime型でも、`CAST(@d AS date)` にしてから同じ式で月初を作れます。
対象月が `YYYYMM` の整数で渡ってくるような場合でも、年と月に分解して `DATEFROMPARTS` に渡すと一貫します。
SQL Serverで月末を取得するSQL(単体)
月末は `EOMONTH(対象日)` が最短です。
| SQL |
|---|
| DECLARE @d date = ‘2026-04-15’; |
| SELECT EOMONTH(@d) AS month_end; |
`EOMONTH` の戻りは日付としての最終日なので、抽出条件で「月末の最後の瞬間」を作るために使う必要はありません。
月末日の表示が必要なら `EOMONTH` を使い、抽出は次の“推奨条件”で切ると混乱しません。
SQL Serverで月次抽出に使うWHERE条件(推奨)
月初は `DATEFROMPARTS`、翌月月初は `DATEADD(MONTH, 1, 月初)` で作れます。
| SQL |
|---|
| DECLARE @d date = ‘2026-04-15’; |
| DECLARE @start date = DATEFROMPARTS(YEAR(@d), MONTH(@d), 1); |
| DECLARE @next date = DATEADD(MONTH, 1, @start); |
| SELECT * |
| FROM orders |
| WHERE order_dt >= @start |
| AND order_dt < @next; |
`order_dt` が `datetime2` のように小数秒を持っていても、この条件は正しく月内を拾えます。
`order_dt` がUTCで保存されている場合は、境界の `@start` と `@next` もUTC基準で作るとズレが減ります。
もし「締め日が月末ではない」などの要件がある場合も、同じ発想で「次の境界未満」を作ると条件が素直になります。
MySQLで月初・月末を取得する方法
MySQLは月末に `LAST_DAY` があり、月初は「その月の1日」を日付型として作るのが基本です。
MySQLでつまずきやすいのは、`DATE`/`DATETIME`/`TIMESTAMP` の扱いが混在したときに、文字列比較や時刻固定でズレが出る点です。
そのため、例では「月初と翌月月初を作って比較する」形を中心にまとめます。
アプリ側から日付型パラメータを渡せるなら、SQL側の文字列整形を減らせるので、まずは入力設計も見直す価値があります。
MySQLで月初を取得するSQL(単体)
月初を作る代表例は、対象日を月初に切り捨てて日付型で返す方法です。
| SQL |
|---|
| — 対象日: ‘2026-04-15’ |
| SELECT DATE_FORMAT(‘2026-04-15’, ‘%Y-%m-01’) AS month_start_str; |
上の例は文字列として月初を作っていますが、比較に使うなら日付型に寄せるのが安全です。
| SQL |
|---|
| SELECT STR_TO_DATE(DATE_FORMAT(‘2026-04-15’, ‘%Y-%m-01’), ‘%Y-%m-%d’) AS month_start; |
アプリ側から月初を `2026-04-01` のように渡せるなら、SQL内で作らず受け取った値をそのまま日付型として使うのが最もシンプルです。
`DATE` と `DATETIME` が混在する場合は、境界値を `DATETIME` にそろえるなど、比較対象の型を意識するとズレが減ります。
MySQLで月末を取得するSQL(単体)
月末の日付は `LAST_DAY(対象日)` で求められます。
| SQL |
|---|
| SELECT LAST_DAY(‘2026-04-15’) AS month_end; |
ただし、`DATETIME` の抽出で「月末の最後の瞬間」を表現しようとして `23:59:59` を固定するのは、ミリ秒相当のデータがあると漏れます。
月末日付の表示が必要なら `LAST_DAY` を使い、抽出用途では次の「翌月月初未満」を作って使うのが安全です。
MySQLで月次抽出に使うWHERE条件(推奨)
月初がわかっていれば、翌月月初は `DATE_ADD(月初, INTERVAL 1 MONTH)` で作れます。
| SQL |
|---|
| — 月初をSQL内で作る場合 |
| SET @d = ‘2026-04-15’; |
| SET @start = STR_TO_DATE(DATE_FORMAT(@d, ‘%Y-%m-01’), ‘%Y-%m-%d’); |
| SET @next = DATE_ADD(@start, INTERVAL 1 MONTH); |
| SELECT * |
| FROM orders |
| WHERE order_dt >= @start |
| AND order_dt < @next; |
`order_dt` が `DATETIME` でも `TIMESTAMP` でも、この条件は「5月に入った瞬間」で確実に止まります。
タイムゾーン変換が絡む環境では、アプリ側でUTCに統一して保存するなど、保存方針と抽出方針を揃えることが重要です。
月次集計を高速化したい場合は、`order_dt` に適切なインデックスがあり、WHERE句で関数をかけていないことを確認します。
PostgreSQLで月初・月末を取得する方法
PostgreSQLは `date_trunc` が強力で、月初は `date_trunc(‘month’, 対象)` の一発で求められます。
一方で `date_trunc` は `timestamp` 系を返すため、カラム型が `date` の場合は型合わせ(`::date` など)を意識すると事故が減ります。
ここでも「算出」と「抽出」を分けて、推奨条件に落とし込みます。
timestamp with time zone を扱う場合は、境界値をどのタイムゾーン基準で作るかも合わせて考えます。
PostgreSQLで月初を取得するSQL(単体)
月初は `date_trunc(‘month’, 対象)` で求められます。
| SQL |
|---|
| — 対象日: DATE ‘2026-04-15’ |
| SELECT date_trunc(‘month’, DATE ‘2026-04-15’) AS month_start_ts; |
日付型として欲しい場合は `::date` を付けて揃えます。
| SQL |
|---|
| SELECT date_trunc(‘month’, DATE ‘2026-04-15’)::date AS month_start; |
カラムがtimestampの場合でも同じ関数で月初に落とせるので、集計の基準づくりが簡単です。
境界値がtimestampでよいなら、無理にdateへ落とさず、そのまま比較するほうが一貫します。
PostgreSQLで月末を取得するSQL(単体)
月末の日付を作るには「翌月月初−1日」の発想が使えます。
| SQL |
|---|
| SELECT (date_trunc(‘month’, DATE ‘2026-04-15’) + interval ‘1 month’ – interval ‘1 day’)::date AS month_end; |
ただし、抽出で必要なのは“月末そのもの”ではなく“次の境界”なので、月末を作らずに `+ interval ‘1 month’` のほうを使うのが安全です。
日時型で「最後の瞬間」を作る必要が出たときも、まずは境界未満で書けないかを検討すると保守性が上がります。
月末日付は表示やレポート出力に限定し、抽出条件には使わないという方針が分かりやすいです。
PostgreSQLで月次抽出に使うWHERE条件(推奨)
月初と翌月月初を `date_trunc` と `interval` で作り、`>=` と `<` で切ります。
| SQL |
|---|
| WITH bounds AS ( |
| SELECT |
| date_trunc(‘month’, DATE ‘2026-04-15’) AS month_start, |
| date_trunc(‘month’, DATE ‘2026-04-15’) + interval ‘1 month’ AS next_month_start |
| ) |
| SELECT o.* |
| FROM orders o |
| CROSS JOIN bounds b |
| WHERE o.order_dt >= b.month_start |
| AND o.order_dt < b.next_month_start; |
`order_dt` が `timestamp` ならこのままでよく、`date` なら境界側を `::date` に揃えるなど、比較する型を合わせるのがポイントです。
集計の実行計画が不安定なときは、境界値をCTEではなくパラメータとして渡すなど、SQLの形を固定すると改善することがあります。
DBごとの月初・月末取得方法まとめ
ここまでの内容を、コピペしやすい形で「月初」「月末」「月次抽出(推奨条件)」に分けて整理します。
算出の式は“日付としての月末”を作る用途に使い、抽出では“翌月月初未満”を優先すると覚えることが減ります。
次の早見表は、まず抽出条件を作り、必要なら月末日付も作るという順に並べています。
抽出条件の列だけを先にコピーして使う運用にすると、境界ミスが起きにくくなります。
早見表:月初式/月末式/月次抽出(推奨条件)
表の「推奨」はすべて「月初以上かつ翌月月初未満」に揃えているため、日時型の精度が変わっても抽出結果がブレにくいです。
表の式はそのまま使える一方で、実際のSQLでは「対象月の渡し方(パラメータ)」を統一するとさらに安全になります。
| DB | 月初(算出) | 月末(日付として算出) | 月次抽出(推奨) |
|---|---|---|---|
| Oracle | `TRUNC(d,’MM’)` | `LAST_DAY(d)` | `dt >= TRUNC(d,’MM’) AND dt < ADD_MONTHS(TRUNC(d,’MM’),1)` |
| SQL Server | `DATEFROMPARTS(YEAR(d),MONTH(d),1)` | `EOMONTH(d)` | `dt >= @start AND dt < DATEADD(MONTH,1,@start)` |
| MySQL | `STR_TO_DATE(DATE_FORMAT(d,’%Y-%m-01′),’%Y-%m-%d’)` | `LAST_DAY(d)` | `dt >= @start AND dt < DATE_ADD(@start,INTERVAL 1 MONTH)` |
| PostgreSQL | `date_trunc(‘month’, d)` | `(date_trunc(‘month’,d)+interval ‘1 month’-interval ‘1 day’)::date` | `dt >= month_start AND dt < (month_start + interval ‘1 month’)` |
テーブル設計としては、日付カラムの型と保存基準(ローカル/UTC)がドキュメント化されているかも確認すると安心です。
月初・月末をWHERE句で使うときの注意点
月初・月末の算出自体は簡単でも、件数が合わない原因の多くはWHERE句の「境界」と「型」にあります。
ここでは、よくある失敗パターンを先に知っておき、最初から安全な条件で書けるようにします。
結論として、月次抽出は「`>= 月初 AND < 翌月月初`」を基本形にすると、ほとんどのズレを避けられます。
さらに、境界値をアプリ側で作れるなら、SQL側の変換を減らし、比較だけに寄せると安定します。
BETWEENの使い方に注意する(終端を含む問題)
`BETWEEN` は両端を含むため、終端(右端)をどう作るかが結果に直結します。
たとえば「月末まで」を `BETWEEN ‘2026-04-01’ AND ‘2026-04-30’` のように書くと、日時型のデータでは `2026-04-30 12:00:00` などが比較対象から外れる可能性があります。
終端を `2026-04-30 23:59:59` にしても、小数秒があるデータは漏れるので、正しく見えても実は欠損が起きます。
そのため、月次抽出では `BETWEEN` を避け、次のように境界未満で切るのが安全です。
| SQL |
|---|
| — NGになりやすい例(終端の作り方で漏れる) |
| — WHERE dt BETWEEN ‘2026-04-01’ AND ‘2026-04-30 23:59:59’ |
| — 推奨(境界未満) |
| WHERE dt >= ‘2026-04-01’ |
| AND dt < ‘2026-05-01’ |
どうしても `BETWEEN` を使う必要がある場合は、終端を“日付”でなく“次の境界−最小単位”にするなど調整が必要ですが、DBや型で最小単位が変わるので保守が難しくなります。
月末を含める要件がある場合でも、実務では「翌月月初未満」に置き換えられないかを先に検討します。
文字列ではなく日付型で比較する(索引・暗黙変換)
文字列比較は見た目が簡単でも、暗黙変換で比較がズレたり、インデックスが使えなくなったりします。
また、カラムを `DATE_FORMAT(dt, …)` のように加工してから比較すると、条件が「カラムに関数をかける形」になり、データ量が増えるほど遅くなりがちです。
基本は「境界値(開始と次の開始)を日付型で作り、カラムはそのまま比較する」形にして、DBに最適化しやすいSQLに寄せます。
タイムゾーンが絡む場合は、保存時点でUTCに統一するか、抽出時点で同じ基準に揃えるかを決め、同じレイヤで変換するようにするとズレの原因を減らせます。
WHERE句の比較が遅いと感じたら、まず「カラム側に関数がないか」「境界が定数またはパラメータになっているか」を点検します。
よくある質問(Q & A)
ここでは、月初・月末のSQLを実務で使うときに出やすい疑問を、短く整理します。
結論だけでなく「なぜ危ないか」を1段落で押さえると、類似ケースにも対応しやすくなります。
月末は31日固定で書いても問題ありませんか
問題が起きやすいのでおすすめしません。
月によって日数は28日から31日まで変わり、うるう年もあるため、31日固定にすると存在しない日付になったり、想定外の月に繰り上がったりします。
月末はDBの関数で求めるか、抽出では「翌月月初未満」で切る形にすると、月日数の差を意識せずに済みます。
一時的に動いて見えても、年跨ぎや2月で破綻しやすいので、最初から関数や境界条件で解決するのが安全です。
月初は文字列で作ってもよいですか
表示やログ用の生成なら文字列でもよいですが、抽出条件に使うなら日付型に寄せるほうが安全です。
文字列のまま比較すると暗黙変換が起きたり、形式の違いで比較が崩れたりするため、SQL内で作る場合も最終的に日付型へ変換してから使います。
アプリ側で `2026-04-01` のような月初を日付型パラメータとして渡せるなら、SQL内の変換を減らせるのでさらに堅牢です。
将来的にDBを変える可能性があるなら、入力設計の段階で「境界は日付型で渡す」を標準化すると移植性が上がります。
月末までのデータ抽出で件数が合わないのはなぜですか
多い原因は「終端を含む条件」と「時刻・タイムゾーンのズレ」です。
`BETWEEN` や `<= 月末` のような条件で月末の最終時刻を固定すると、小数秒を持つデータが漏れることがあります。
また、保存がUTCで表示がローカル時刻のように基準が混ざると、月境界がずれて“月がまたいだ”扱いになるため、保存基準と抽出基準を揃え、`>= 月初 AND < 翌月月初` の形で切ると解決しやすいです。
まずは境界の2点(開始と次の開始)をログ出力し、想定どおりの時刻になっているかを確認すると切り分けが早くなります。
まとめ
月初・月末の算出はDBごとに関数が違いますが、実務では「月初を作る」「翌月月初を作る」「`>=` と `<` で切る」の3点を押さえると安定します。
Oracleは `TRUNC` と `ADD_MONTHS`、SQL Serverは `DATEFROMPARTS` と `DATEADD`、MySQLは `LAST_DAY` と `INTERVAL`、PostgreSQLは `date_trunc` と `interval` を軸に覚えると迷いません。
件数ズレが出たら、終端を含む条件や文字列比較をしていないかを見直し、まずは「`>= 月初 AND < 翌月月初`」に揃えるところから始めるのがおすすめです。
最後に、抽出条件は「月末日付を作る」発想よりも「次の境界未満」で切る発想に寄せると、DB方言が違っても再利用しやすくなります。