Loading

ROUTE

ルートゼロの
アクティビティ

PostgreSQLのdate_truncで月別集計|注文データを数えるSQL例

1

「注文日時は日付と時刻まで保存されているのに、月ごとの件数を数えたい」。PostgreSQLでは`date_trunc('month', 日時)`で日時を月初へそろえ、`GROUP BY`で同じ月の行をまとめられます。この記事では、テーブルを作らず試せるサンプルSQLと、その結果の読み方を説明します。

結論:date_truncで月初にそろえてからGROUP BYする

`date_trunc`は日付時刻を指定した単位に切り捨てるPostgreSQLの関数です。`month`を指定すると、同じ年・同じ月の日時はその月の1日0時0分にそろいます。

SELECT date_trunc('month', TIMESTAMP '2026-02-23 14:35:20');
-- 想定結果: 2026-02-01 00:00:00

月別集計では、このそろえた月初値を`GROUP BY`のキーにします。`EXTRACT(MONTH FROM …)`で月の数字だけを取り出す場合と違い、`date_trunc`なら2025年1月と2026年1月を誤って同じグループにするのを避けやすくなります。

用語解説:GROUP BY
同じ値を持つ行をまとめ、COUNTやSUMなどの集計関数で件数・合計を計算するSQLの指定です。


準備なしで動かせる月別集計SQL

以下はPostgreSQLのSQL入力画面(`psql`やSQLエディター)で実行できます。`WITH orders AS (…)`はこのクエリだけで使える仮のデータを用意する構文なので、実テーブルを作成したり更新したりしません。

WITH orders (ordered_at, amount) AS (
  VALUES
    (TIMESTAMP '2026-01-12 10:00:00', 1200),
    (TIMESTAMP '2026-01-28 18:30:00',  800),
    (TIMESTAMP '2026-02-02 09:15:00', 1500)
)
SELECT
  date_trunc('month', ordered_at)::date AS month_start,
  COUNT(*) AS order_count,
  SUM(amount) AS total_amount
FROM orders
GROUP BY date_trunc('month', ordered_at)::date
ORDER BY month_start;

想定結果は1月が2件・合計2000、2月が1件・合計1500です。SQLを実行できる環境があれば、返った結果と照合してください。

month_start order_count total_amount
2026-01-01 2 2000
2026-02-01 1 1500

`::date`はPostgreSQLの型変換の書き方です。今回は月初の日時のうち日付だけを見やすく表示しています。集計キーにも同じ変換式を指定し、何でグループ化したかを明確にしました。


クエリを1行ずつ読む:どこで月をまとめている?

  1. `VALUES`で注文日時と金額の3行を用意する。
  2. `date_trunc`で各日時を2026-01-01または2026-02-01にそろえる。
  3. `GROUP BY`で同じ月初日の行をまとめる。
  4. `COUNT(*)`で行数を、`SUM(amount)`で金額合計を計算する。
  5. `ORDER BY`で古い月から表示する。

まずは合計を外して`COUNT(*)`だけにしてみると、どの行が同じグループになるか理解しやすくなります。次にサンプルの1件を3月へ変え、3か月分の結果が返るか確認してください。

(GROUP BYそのものに慣れていない場合については『GROUP BY使い方完全解説|SQLエラー・WHERE HAVINGの違いは?』をご参照ください)


月別集計がずれるときの確認点

最初に、日時列の型が`timestamp without time zone`か`timestamp with time zone`(`timestamptz`)かを確かめましょう。前者は入力した日時の数字をそのまま扱います。後者の月境界は、セッションのタイムゾーン設定に影響されます。

例えばUTCでは1月31日23時30分でも、日本時間では2月1日8時30分です。日本時間の売上集計をしたいなら、どのタイムゾーンの月を数えるかを決める必要があります。

-- paid_at が timestamptz 型の場合の考え方
SELECT
  date_trunc(
    'month',
    TIMESTAMPTZ '2026-01-31 23:30:00+00'
      AT TIME ZONE 'Asia/Tokyo'
  )::date AS month_in_japan;
-- 想定結果: 2026-02-01

上の例では`AT TIME ZONE`で日本時間の時刻表現へ変えてから月初にそろえています。実テーブルの列型・保存方式に応じた変換が必要で、時刻が最初から日本時間の`timestamp`列なら二重変換を避けます。

  • 1月・2月の境界にある1件をサンプルに入れ、どちらの月に入ったか確認する。
  • `COUNT(*)`と`SUM(amount)`を混同しない。金額がNULLの場合のSUMの扱いは別途確認する。
  • `GROUP BY`の式とSELECTの式が整合しているか確認する。
  • 画面表示の`to_char`で文字列にする前に、日時の月境界が正しいか確かめる。

よくある質問と次の練習

date_truncはMySQLでもそのまま使えますか?

この記事はPostgreSQL向けです。MySQLには同じ関数名が標準で存在しないため、日付関数を製品ごとに選んでください。

月ごとに数字の1〜12だけでGROUP BYしてもよいですか?

複数年のデータを扱う場合は年を区別しないと別の年の同月を混ぜます。月初の日付でまとめるか、年と月の両方を集計キーにします。

(SQLの日付関数を他のデータベースと比較したい場合については『SQL日付関数の完全ガイド|現場で使えるテンプレ・失敗回避法も解説』をご参照ください)

次は実データを変更せず、`WHERE ordered_at >= … AND ordered_at < …`を使った期間抽出を組み合わせてみましょう。集計する単位と集計の対象期間を分けて考えると、月別レポートのSQLを読みやすくできます。

ルートゼロの採用情報を見る

RANKINGranking-icon

すべてを開示している会社です

ポジティブな面もネガティブな面も、包み隠さず誠実にお答えします。
気になることがあれば、面談の際にすべてオープンにお話します。