※ この記事にはアフィリエイトリンクが含まれます
PostgreSQLで月ごと集計をしようとして、思ったように集計できず悩んでいませんか?
・日付から月を抽出する方法がわからない
・集計ラベルを見やすくしたいが書式がわからない
・欠損する月(データがない月)もレポートに含めたい
結論から言うと、PostgreSQLのdate_truncやto_char、generate_seriesなどを組み合わせれば、月ごと集計は簡単にできます。
- date_truncで月単位に丸める
- to_charで見やすいラベルにする
- generate_seriesで欠損月を補完する
この記事では、初心者向けに基本から実践的な応用まで、サンプルSQLを交えて解説します。
読み終えることで、実務で使える月ごと集計のパターンが理解できます。
月ごと集計の基本:date_truncを使う
まず最初に押さえたいのはdate_trunc関数です。
date_trunc('month', timestamp)は日時をその月の開始日時に丸めます。これをGROUP BYに使うと簡単に月ごと集計ができます。
基本的な集計クエリの例
-- 売上テーブル: sales(id, amount, sold_at)
SELECT
date_trunc('month', sold_at) AS month,
sum(amount) AS total_amount,
count(*) AS cnt
FROM sales
GROUP BY date_trunc('month', sold_at)
ORDER BY month;
ポイントはdate_truncで統一した時刻をGROUP BYに使うことです。
理由は、timestamp型をそのままGROUP BYすると日付の時刻部分でグループが分かれてしまうためです。
ラベルを見やすくする:to_charの活用
集計結果をレポートやCSVで出すときは、日付を文字列で整形すると見やすくなります。
PostgreSQLのto_char関数で’YYYY-MM’などの形式に変換できます。
月ラベルを付ける例
SELECT
to_char(date_trunc('month', sold_at), 'YYYY-MM') AS month_label,
sum(amount) AS total_amount
FROM sales
GROUP BY 1
ORDER BY 1;
ポイントはGROUP BYで同じ式やラベル番号(ここでは1)を使うことです。
to_charは表示専用なので、内部での比較はdate_truncを使う方が安全です。
欠損月を含める:generate_seriesで月を作る
データが存在しない月もレポートに表示したい場合があります。
その場合はgenerate_seriesで月の連続系列を作り、LEFT JOINで結合します。
欠損月を補完するクエリ例
WITH months AS (
SELECT generate_series(
date_trunc('month', min(sold_at)),
date_trunc('month', max(sold_at)),
interval '1 month'
) AS month
FROM sales
)
SELECT
to_char(m.month, 'YYYY-MM') AS month_label,
coalesce(s.total_amount, 0) AS total_amount
FROM months m
LEFT JOIN (
SELECT date_trunc('month', sold_at) AS month, sum(amount) AS total_amount
FROM sales
GROUP BY date_trunc('month', sold_at)
) s ON m.month = s.month
ORDER BY m.month;
ポイントは、まず集計対象の範囲で月系列を作ることです。
その上でLEFT JOINしてNULLを0に変換することで、欠損月が0として表示されます。
時間帯(タイムゾーン)と集計の注意点
timestamp with time zoneを扱う場合、タイムゾーンによってdate_truncの結果が変わることがあります。
UTC基準で集計したいのか、ローカルタイムで集計したいのかを明確にしましょう。
タイムゾーンを明示する例
-- UTCで月ごと集計
SELECT date_trunc('month', sold_at AT TIME ZONE 'UTC') AS month_utc, sum(amount)
FROM sales
GROUP BY 1
ORDER BY 1;
-- 日本時間で集計
SELECT date_trunc('month', sold_at AT TIME ZONE 'Asia/Tokyo') AS month_jst, sum(amount)
FROM sales
GROUP BY 1
ORDER BY 1;
理由は、サーバーやアプリのタイムゾーン設定が異なると、集計結果の境界がズレるためです。
特に深夜付近のトランザクションが多いサービスでは注意が必要です。
パフォーマンス改善のポイント
大規模データで頻繁に月ごと集計をする場合は、適切なインデックスや事前集計を検討しましょう。
以下の手法が有効です。
インデックスとマテリアライズドビュー
sold_at列に対して直接インデックスを貼るのは基本です。
さらに、date_trunc('month', sold_at)での検索や結合が多い場合は、式インデックスも検討できます。
-- 式インデックスの例
CREATE INDEX idx_sales_month ON sales (date_trunc('month', sold_at));
-- マテリアライズドビューで事前集計
CREATE MATERIALIZED VIEW mv_sales_month AS
SELECT date_trunc('month', sold_at) AS month, sum(amount) AS total_amount
FROM sales
GROUP BY date_trunc('month', sold_at);
-- 定期的にREFRESH MATERIALIZED VIEWで更新
REFRESH MATERIALIZED VIEW mv_sales_month;
ポイントは、計算コストの高い処理は事前にまとめておくことです。
マテリアライズドビューはリアルタイム性を犠牲にする代わりに高速な読み取りを実現します。
月の開始を変更したい(会計年度や締め日対応)
会計上の締め日が毎月15日のような場合、単純なdate_truncでは対応できません。
その場合は日付をシフトしてからdate_truncするテクニックを使います。
締め日が15日の例
-- 締め日が毎月15日の場合は15日を基準にシフトしてから丸める
SELECT
to_char(date_trunc('month', sold_at - interval '15 days') + interval '15 days', 'YYYY-MM') AS billing_month,
sum(amount) AS total_amount
FROM sales
GROUP BY 1
ORDER BY 1;
手順は、まず基準日だけ日時を引き、それで月始めに丸めることです。
最後に元の基準を戻せば、締め日ベースの月が得られます。
複雑な条件での月ごと集計
商品カテゴリや地域別など複数条件で月ごと集計したいケースはよくあります。
GROUP BYに複数の項目とdate_truncを組み合わせることで対応できます。
カテゴリ別・月別集計の例
SELECT
category,
date_trunc('month', sold_at) AS month,
sum(amount) AS total_amount
FROM sales
GROUP BY category, date_trunc('month', sold_at)
ORDER BY category, month;
ポイントはGROUP BYの順序に注意することです。
表示の順序はORDER BYで調整すると見やすくなります。
よくあるトラブルとその対処法
初心者がはまりやすい点をまとめます。
- date_truncの使い方を誤り、日付がバラバラに集計される
- タイムゾーンの違いで境界がずれる
- 欠損月を表示できずレポートに穴ができる
- 大データでクエリが遅くなる
それぞれ、今回紹介したdate_trunc、AT TIME ZONE、generate_series、インデックス/マテリアライズドビューで解決可能です。
最後にまとめます。PostgreSQLで月ごと集計する際はまずdate_truncを使い、必要に応じてto_charでラベル化します。
欠損月の補完やタイムゾーン、パフォーマンス対策も押さえておけば、実務で困ることは少なくなります。
この記事のサンプルを実際に動かして、自分のデータに合わせて調整してみてください。
PostgreSQLは、現場でも広く使われている信頼性の高いデータベースです。もしこれから本格的に学び、実務で通用する力をつけたい方には、RareTECHをチェックしてみてください。実案件ベースのカリキュラムで、あなたのスキルを次のステージへ引き上げてくれるはずです。
なんくる「本当にエンジニアとしてやっていけるか不安…」という方も、実践的な開発に関わることで、転職後の働き方を事前に体感できますよ。
実務で使えるDBスキルとともに、プログラミングスキルをちゃんと身につけたいなら、
RareTECHの無料カウンセリングで、学ぶ目的やゴールをプロと一緒に明確にしてみましょう。独学では得られない「実践的な成長の道筋」が見えてきます。


もしこの内容を通して、PostgreSQLについてさらに理解を深めたいと感じられたなら、信頼できる講座や書籍を紹介した別記事をご覧いただくのも良いかと思います。ご自身の学びに、きっとお役立ていただけるはずです。










コメント