【PostgreSQL】週単位の集計方法をわかりやすく解説

※ この記事にはアフィリエイトリンクが含まれます

週単位での集計がうまくできずに困っていませんか?

  • date_truncやweek関数の使い方がよく分からない
  • 年をまたいだ集計や抜けている週の扱いで詰まる
  • 集計が遅くて本番で使えない

結論から言うと、PostgreSQLのdate_truncやgenerate_seriesを組み合わせれば
週単位の正確で高速な集計が可能です。

  • date_trunc('week', timestamp)で週の開始日時に揃える
  • generate_seriesで空週(データがない週)も0で表示する
  • インデックスやマテリアライズドビューでパフォーマンス改善

この記事では、基本的なSQLの書き方から実践的な注意点、
パフォーマンス対策まで初心者向けにわかりやすく解説します。

目次

週単位集計の基本的な考え方

まず押さえるべきは「週をどう定義するか」です。
PostgreSQLではdate_trunc('week', ...)がよく使われます。

date_trunc('week', timestamp)は、その週の開始時刻に切り捨てます。
デフォルトでは週の開始は月曜日です(ISO準拠)。

簡単な集計の例

次の例は、ordersテーブルを週単位で件数を集計する基本形です。
実務で最初に試すクエリになります。

SELECT
  date_trunc('week', created_at) AS week_start,
  COUNT(*) AS orders_count
FROM orders
GROUP BY date_trunc('week', created_at)
ORDER BY week_start;

このクエリは週の開始(00:00:00)でグルーピングします。
結果は週ごとの集計になりますが、いくつか注意点があります。

よくある問題とその理由

初心者がつまずきやすいポイントは主に3つあります。
それぞれ理由を簡潔に説明します。

  • 年をまたぐ週の扱い:週番号だけでGROUP BYすると年をまたいだと混同する
  • データが存在しない週の欠損:そのままだと週が抜けて表示されない
  • パフォーマンス:生データ全件を毎回集計すると時間がかかる

年をまたいだ週番号の問題

例えば extract(week from timestamp)to_char(...,'WW') は年の境界で
同じ週番号が出ることがあり、単純に週番号でグループすると誤集計になります。
年情報も一緒に扱う必要があります。

年を含める方法は複数ありますが、最も安全なのは週の開始日時で
グループすることです(date_truncを使う)。
これなら日付範囲がユニークになります。

欠損週(データがない週)を0で出す方法

BIレポートでは「データが無い週も0として表示」したい場面が多いです。
この場合は generate_series を使って週ごとの日時列を作成します。

generate_seriesで週の系列を作る例

以下は指定期間の週開始日時を生成し、ordersの集計とLEFT JOINする方法です。
NULLをCOALESCEで0に変換しています。

WITH weeks AS (
  SELECT generate_series(
    date_trunc('week', '2024-01-01'::timestamp),
    date_trunc('week', '2024-12-31'::timestamp),
    '1 week'::interval
  ) AS week_start
), orders_by_week AS (
  SELECT date_trunc('week', created_at) AS week_start,
         COUNT(*) AS orders_count
  FROM orders
  WHERE created_at >= '2024-01-01'::date
    AND created_at < '2025-01-01'::date
  GROUP BY date_trunc('week', created_at)
)
SELECT w.week_start,
       COALESCE(o.orders_count, 0) AS orders_count
FROM weeks w
LEFT JOIN orders_by_week o USING (week_start)
ORDER BY w.week_start;

この方法なら、期間内のすべての週が結果に含まれます。
グラフ表示や時系列分析でよく使うパターンです。

年をまたいだ集計の正しいやり方

年をまたぐ場合、ISO週(IYYY/IW)を使う方法と、週開始日時で扱う方法があります。
実務では週開始日時で扱うのが最も直感的で安全です。

ISO週を使いたい場合の注意

ISO週は to_char(created_at, 'IYYY-IW') のように年と週を文字列で取得します。
ただし扱いがやや複雑なので、集計キーとしては日付に変換しておくと便利です。

SELECT
  to_char(date_trunc('week', created_at), 'IYYY-IW') AS iso_year_week,
  COUNT(*)
FROM orders
GROUP BY iso_year_week
ORDER BY iso_year_week;

ISO週を表示したいダッシュボード用途には向いていますが、
内部計算はdate_trunc('week', ...)で統一すると管理が簡単です。

パフォーマンス改善のポイント

大量データを週単位で集計する際は、クエリ最適化が必須です。
ここでは初心者でも取り組みやすい対策を紹介します。

1. 適切なインデックスを張る

created_atに対して通常のBTREEインデックスを張るのが基本です。
WHERE句で日付範囲を絞ればインデックスが有効になります。

CREATE INDEX idx_orders_created_at ON orders(created_at);

さらに、頻繁にdate_trunc('week', created_at)で集計するなら
関数インデックスを作ることも検討できます。ただし用途を限定してください。

CREATE INDEX idx_orders_week_trunc ON orders (date_trunc('week', created_at));

2. マテリアライズドビューの活用

毎回生データを集計するのが重い場合、マテリアライズドビューを使って
定期的に集計結果を保存する方法が効果的です。バッチ更新で高速化できます。

CREATE MATERIALIZED VIEW mv_orders_weekly AS
SELECT date_trunc('week', created_at) AS week_start,
       COUNT(*) AS orders_count
FROM orders
GROUP BY date_trunc('week', created_at);

-- 更新
REFRESH MATERIALIZED VIEW CONCURRENTLY mv_orders_weekly;

CONCURRENTLYを使えば読みながら刷新できますが、追加の要件があります。
運用に合わせてスケジュールを組みましょう。

3. パーティショニング

テーブルが非常に大きい場合は日付でパーティショニングすると集計が速くなります。
週単位で検索する際も不要なパーティションをスキップできます。

実務で注意する細かい点

集計時の落とし穴や運用面の注意点をまとめます。
これらを押さえておけばトラブルを減らせます。

  • タイムゾーン:timestamp with time zone と without に注意する
  • 夏時間(DST):日付境界がずれるケースを意識する
  • 集計粒度の固定:週の定義をチームで統一する

タイムゾーンとtimestampの扱い

timestamp without time zoneはDBにそのまま保存されます。
ユーザーのタイムゾーンで解釈したい場合は注意が必要です。

timezoneを揃えて集計するには AT TIME ZONE を使います。
UTCで保存している場合は集計前にローカル時間に変換することが大切です。

SELECT
  date_trunc('week', created_at AT TIME ZONE 'UTC' AT TIME ZONE 'Asia/Tokyo') AS week_start_jst,
  COUNT(*)
FROM orders
GROUP BY week_start_jst;

まとめと実践的な推奨

最後に、この記事の要点と実務での推奨手順をまとめます。
すぐに試せる順番で書いています。

  1. まずは date_trunc('week', created_at) で集計して結果を確認する
  2. 必要なら generate_series で欠損週を補完する
  3. インデックスやマテリアライズドビューでパフォーマンスを改善する
  4. タイムゾーンやISO週など定義をチームで統一する

この流れを踏めば、週単位の集計で迷うことはぐっと減ります。
実例のSQLを試しながら、自分のデータに合わせて調整してみてください。

もし具体的なテーブル定義や実行計画(EXPLAIN)の結果があれば、
それを元にさらにパフォーマンス改善のアドバイスも可能です。

この記事がPostgreSQLでの週単位集計の第一歩になれば幸いです。
疑問や具体例があれば気軽に質問してください。


PostgreSQLは、現場でも広く使われている信頼性の高いデータベースです。もしこれから本格的に学び、実務で通用する力をつけたい方には、RareTECHをチェックしてみてください。実案件ベースのカリキュラムで、あなたのスキルを次のステージへ引き上げてくれるはずです。

なんくる

「本当にエンジニアとしてやっていけるか不安…」という方も、実践的な開発に関わることで、転職後の働き方を事前に体感できますよ。

実務で使えるDBスキルとともに、プログラミングスキルをちゃんと身につけたいなら、
RareTECH無料カウンセリングで、学ぶ目的やゴールをプロと一緒に明確にしてみましょう。独学では得られない「実践的な成長の道筋」が見えてきます。


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

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

沖縄出身のエンジニアです。IT業界で5年以上の経験があり、主にC#やPHPを使って開発を行ってきました。新しい技術にも興味があり、日々学びながらスキルアップを目指しています。

コメント

コメントする

CAPTCHA


目次