0Pricing
Coding Interview Prep · レッスン

日付の切り捨てとバケット化

DATE_TRUNCなどの同等機能を使って、週・月・四半期ごとにグループ化します。

「日付の切り捨てとバケット化」はCoddyKit上の無料Coding Interview Prepレッスンです。 これはレッスン2/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはCoding Interview Prep学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Coding Interview Prepコースには全4レッスンが含まれています。

なぜ日付のバケット化が問われるのか

「週ごとの売上」や「月ごとのアクティブユーザー」は、アナリスト職の面接で頻出する題材です。ここで試されているのは、正確なタイムスタンプをより粗いバケットにまとめ、行をグループ化できるかどうかです。

初学者がよくする間違いは、月番号だけを取り出して、異なる年の同じ月をまとめてしまうことです。実務的な答えは切り捨てです。すべてのタイムスタンプを、その期間の開始時点に対応付けます。

  • 週、月、四半期、年のバケット
  • DATE_TRUNC と各方言に相当する関数
  • グラフの軸が正しく揃うようにグループ化すること

DATE_TRUNC:中核となるツール

PostgreSQL では、DATE_TRUNC(unit, ts) を使うと、指定した単位より細かい部分がすべてゼロになります。'month' に切り捨てると、3月のどのタイムスタンプも 2024-03-01 00:00:00 になります。

戻り値は引き続きタイムスタンプなので、時系列順に並べ替えられ、きれいにグループ化できます。レポート作成で最も役立つ日付関数です。

SELECT DATE_TRUNC('month', TIMESTAMP '2024-03-17 14:30:00');
-- 2024-03-01 00:00:00

月ごとの売上をグループ化する

典型的な実践例です。タイムスタンプを月単位に切り捨ててから、グループ化して合計します。バケットに年の情報も含まれるため、2023年1月と2024年1月は別々に扱われます。

切り捨てた値で並べ替えると、グラフにそのまま使える、見やすい時系列になります。

SELECT
  DATE_TRUNC('month', order_ts) AS month,
  SUM(amount)                   AS revenue
FROM orders
GROUP BY 1
ORDER BY 1;

EXTRACT と DATE_TRUNC の違い

面接では、この違いを直接確認されることがあります。どちらも期間の情報を取り出しますが、答える問いが異なります。

  • EXTRACT(MONTH FROM ts) は、年に関係なく3月なら3という数値を返すため、季節性の分析に適しています。
  • DATE_TRUNC('month', ts) は特定の月の開始時点を返すため、年を区別でき、時系列に適しています。

月次トレンドのグラフで EXTRACT(MONTH ...) を使ってグループ化すると、気付かないうちに異なる年が混ざってしまいます。

-- Seasonality: which month is busiest on average?
SELECT EXTRACT(MONTH FROM order_ts) AS month_num, COUNT(*)
FROM orders GROUP BY 1 ORDER BY 1;

-- Time series: month-by-month trend (years kept separate)
SELECT DATE_TRUNC('month', order_ts) AS month, COUNT(*)
FROM orders GROUP BY 1 ORDER BY 1;

週のバケットと月曜日の問題

週単位のグループ化には、面接官が好んで問う微妙な論点があります。週はいつ始まるのでしょうか。PostgreSQL の DATE_TRUNC('week', ts) は常に月曜日(ISO週)に切り捨てます。

業務上、日曜日始まりの週が必要なら、ずらしが必要です。よく使われる方法は、日付を1日戻してから切り捨て、その後1日進めることです。

-- ISO week (Monday start)
SELECT DATE_TRUNC('week', order_ts) AS iso_week FROM orders;

-- Sunday-start week
SELECT DATE_TRUNC('week', order_ts + INTERVAL '1 day') - INTERVAL '1 day'
  AS sunday_week
FROM orders;

四半期のバケット

四半期単位のレポートは、金融に近い職種でよく使われます。DATE_TRUNC('quarter', ts) は、任意のタイムスタンプをその四半期の初日に対応付けます。つまり、1月1日、4月1日、7月1日、10月1日のいずれかになります。

四半期を番号で表したい場合は、EXTRACT(QUARTER ...) と年を組み合わせます。

SELECT
  DATE_TRUNC('quarter', order_ts)                  AS quarter_start,
  EXTRACT(YEAR FROM order_ts) || '-Q'
    || EXTRACT(QUARTER FROM order_ts)              AS quarter_label,
  SUM(amount)                                      AS revenue
FROM orders
GROUP BY 1, 2
ORDER BY 1;

MySQL には DATE_TRUNC がない

方言をまたいだ質問でよくあるものに、「MySQL に DATE_TRUNC がない場合、月単位でバケット化するにはどうしますか」があります。移植性を重視した答えは、日付を必要な粒度までフォーマットすることです。

  • DATE_FORMAT(ts, '%Y-%m-01') は、月初をテキストまたは日付として返します。
  • DATE_FORMAT(ts, '%Y-%m') は、2024-03 のような並べ替え可能な文字列キーを返します。

週については、MySQL の YEARWEEK() に、週の開始曜日を制御するモード引数を指定できます。

-- MySQL month bucket
SELECT DATE_FORMAT(order_ts, '%Y-%m-01') AS month, SUM(amount)
FROM orders
GROUP BY 1
ORDER BY 1;

SQL Server でのバケット化

SQL Server には従来、直接的な切り捨て関数がなかったため、候補者は DATEFROMPARTS や DATEADD/DATEDIFF のイディオムを使っていました。現在のバージョン(2022以降)では DATETRUNC が追加されています。

「基準時点からの単位数を数え、その数を再び加算する」という古典的なイディオムは、どのバージョンでも使えるため、知っておく価値があります。

-- Portable SQL Server month truncation
SELECT DATEADD(month, DATEDIFF(month, 0, order_ts), 0) AS month_start
FROM orders;

-- SQL Server 2022+
SELECT DATETRUNC(month, order_ts) AS month_start FROM orders;

時系列の欠損を埋める

切り捨てるだけでは、行が0件の期間が失われます。注文が1件もない月は、単に結果に現れません。面接では、この点に気付けるかどうかが試されます。

解決策は、すべての期間からなる完全なスパインを生成し、そこにデータをLEFT JOINすることです。Postgres では generate_series を使ってスパインを作成できます。

SELECT
  cal.month,
  COALESCE(SUM(o.amount), 0) AS revenue
FROM generate_series(DATE '2024-01-01', DATE '2024-12-01',
                      INTERVAL '1 month') AS cal(month)
LEFT JOIN orders o
  ON DATE_TRUNC('month', o.order_ts) = cal.month
GROUP BY cal.month
ORDER BY cal.month;

発展例:週ごとのアクティブユーザー

バケット化と重複を除いたカウントを組み合わせます。「週次アクティブユーザー」とは、実際のプロダクト分析でよく求められる、週のバケットごとのユニークユーザー数です。

イベントのタイムスタンプを週単位に切り捨ててから、COUNT(DISTINCT user_id) を使います。アクティビティが0の週も表示するために週のスパインを結合すると説明できれば、さらに評価されます。

SELECT
  DATE_TRUNC('week', event_ts) AS week,
  COUNT(DISTINCT user_id)      AS wau
FROM events
GROUP BY 1
ORDER BY 1;

インデックス付きの列をバケット化する

触れておきたいパフォーマンス上の注意点が1つあります。WHERE 句の中で日付列を DATE_TRUNC で包むと、プランナーがその列のインデックスを使えなくなることがあります。

GROUP BY で使う分には問題ありませんが、絞り込みでは、計算した境界値と元の列を比較してください。前に扱った半開区間のパターンが、ここでも適用できます。

-- Avoid in WHERE: DATE_TRUNC('month', order_ts) = '2024-03-01'
-- Prefer:
SELECT * FROM orders
WHERE order_ts >= DATE '2024-03-01'
  AND order_ts <  DATE '2024-04-01';

確認問題

年を分けたまま月次トレンドのグラフを作るには、どのツールを選べばよいでしょうか。

復習:日付の切り捨てとバケット化

覚えておくこと:

  • DATE_TRUNC(unit, ts) はタイムスタンプを期間の開始時点に対応付け、年も区別するため、時系列に適したツールです。
  • EXTRACT は単純な数値を返します。季節性には適していますが、異なる年が混ざります。
  • Postgres の週は月曜日に始まります。日曜日始まりが必要なら、ずらしてください。
  • MySQL では DATE_FORMAT を使います。古い SQL Server では DATEADD(DATEDIFF(...)) のイディオムを使い、2022以降では DATETRUNC が使えます。
  • 空の期間を表示するには、生成した日付スパイン + LEFT JOINを使い、インデックスの利用を維持するために WHERE 句では DATE_TRUNC を使わないようにします。

よくある質問

「日付の切り捨てとバケット化」レッスンは無料ですか?

はい。「日付の切り捨てとバケット化」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Coding Interview Prepコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Coding Interview Prepコースには全4レッスンが含まれています。

「日付の切り捨てとバケット化」で何を学びますか?

DATE_TRUNCなどの同等機能を使って、週・月・四半期ごとにグループ化します。 ブラウザで直接実行するハンズオンコードでCoding Interview Prepを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Coding Interview Prepを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのCoding Interview Prepは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン2/4です。

「日付の切り捨てとバケット化」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このCoding Interview Prepレッスンでコードを書いて実行できますか?

はい。すべてのCoding Interview Prepレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. 日付の計算とインターバル
  2. 日付の切り捨てとバケット化
  3. 文字列の解析とフォーマット
  4. タイムゾーンとタイムスタンプ
← Coding Interview Prepに戻る