日付関数の活用
月が変わるたびに、SQL の日付を書き換えている
「先月の分を出して」と頼まれるたびに '2026-04-01' のような日付を手で書き換えていると、書き換え忘れが必ず起きます。しかも、そのクエリは毎日動かすバッチに入れられません。実行するたびに人が日付を直さないと、古い期間を集計し続けてしまうからです。
今日が何日かは、データベースが知っています。CURRENT_DATE で今日を取り出し、INTERVAL で期間を足し引きすると、実行した日を基準にした条件が書けます。
SQL クエリ
SELECT id, title, published_at
FROM articles
WHERE published_at >= CURRENT_DATE - INTERVAL '7 days';「今日から 7 日前より後に公開された記事」です。日付を 1 つも書いていないので、いつ実行しても直近 7 日間を返します。INTERVAL '1 month' や INTERVAL '3 hours' のように、単位は日以外も指定できます。
31 日で書いた条件は、2 月に壊れる
ある月をまるごと取り出したいとき、末日を書いてしまうと事故ります。月によって末日が 28 日だったり 31 日だったりするからです。「その月の 1 日以上、翌月の 1 日より前」と書けば、どの月でも同じ形で通ります。
SQL クエリ
SELECT id, title, published_at
FROM articles
WHERE published_at >= DATE '2026-04-01'
AND published_at < DATE '2026-04-01' + INTERVAL '1 month';後ろが <= ではなく < である点が要です。列が日付ではなく日時で入っている場合、<= '2026-04-30' と書くと 4 月 30 日の 0 時 0 分 1 秒より後がすべてこぼれ落ちます。「翌月の頭より前」なら、時刻が入っていても取りこぼしません。
動くけれど、遅い書き方
年と月をそれぞれ取り出して比べる書き方もできます。
SQL クエリ
WHERE EXTRACT(YEAR FROM published_at) = 2026
AND EXTRACT(MONTH FROM published_at) = 4読みやすいのですが、これは避けます。列を関数でくるむと、その列に索引が張ってあっても使えなくなり、テーブル全体を 1 行ずつ調べることになるからです。行数が増えたときにはっきり差が出ます。範囲で書ける条件は範囲で書く、と覚えておいてください。
現場の話 日時をどのタイムゾーンで保存するかは、集計結果を丸 1 日ずらす力があります。保存は UTC で統一し、表示と集計のときに
AT TIME ZONE 'Asia/Tokyo'で変換する、という分担が一般的です。「日本時間の 1 日」は UTC では前日の 15 時から始まるので、日付の境目をまたぐ行の扱いがずれます。
テーブル構造
CREATE TABLE orders (
id INT PRIMARY KEY,
customer_name VARCHAR(50) NOT NULL,
amount INT NOT NULL,
ordered_at DATE NOT NULL
);
INSERT INTO orders (id, customer_name, amount, ordered_at) VALUES
(1, '田中', 5000, '2026-03-10'),
(2, '佐藤', 12000, '2026-04-05'),
(3, '鈴木', 8000, '2026-04-15'),
(4, '高橋', 15000, '2026-04-28'),
(5, '伊藤', 3000, '2026-05-02'),
(6, '渡辺', 22000, '2026-05-08'),
(7, '山田', 7500, '2026-05-15'),
(8, '中村', 9800, '2026-05-18');期待される出力
| id | customer_name | amount | ordered_at |
|---|---|---|---|
| 5 | 伊藤 | 3000 | 2026-05-02 |
| 6 | 渡辺 | 22000 | 2026-05-08 |
| 7 | 山田 | 7500 | 2026-05-15 |
| 8 | 中村 | 9800 | 2026-05-18 |