Zenn
Open1

DuckDBメモ

Yoshiaki KawazuYoshiaki Kawazu

日付関連

とりあえずよく使うやつ。

SELECT
  first_day: date_trunc('month', '2025-03-23'::date),
  last_day:  last_day('2025-03-23'::date);
┌────────────┬────────────┐
│ first_day  │  last_day  │
│    datedate    │
├────────────┼────────────┤
│ 2025-03-012025-03-31 │
└────────────┴────────────┘

前月末とか翌月末とかが必要な場合は '1 month'::INTERVAL を必要な分だけ足し引きしてやれば良い。ちなみに '1 month'::INTERVAL の整数倍で足し引きする限りにおいては月の日数の違いは良い感じに扱ってくれるので助かる。

SELECT
  last_month:  base_date - '1 month'::interval,
  base_date,
  next_month: base_date + '1 month'::interval,
FROM (values
  ('2025-03-01'::date),
  ('2025-03-14'::date),
  ('2025-03-31'::date), -- ←この前後の日付に注目
) t(base_date);
┌─────────────────────┬────────────┬─────────────────────┐
│     last_month      │ base_date  │     next_month      │
│      timestampdatetimestamp      │
├─────────────────────┼────────────┼─────────────────────┤
│ 2025-02-01 00:00:002025-03-012025-04-01 00:00:00 │
│ 2025-02-14 00:00:002025-03-142025-04-14 00:00:00 │
│ 2025-02-28 00:00:002025-03-312025-04-30 00:00:00 │
└─────────────────────┴────────────┴─────────────────────┘

マクロ定義

DuckDBでは関数みたいな感じでマクロが定義できる。引数の数の違いでオーバーロードしたりデフォルト値の指定や名前付き引数なども使える。
https://duckdb.org/docs/stable/sql/statements/create_macro.html

試しに前月末日、翌月1日、翌月末日、とか経理でよく使う日付を取得するマクロを定義してみる。

-- 月の初日を返すマクロ
CREATE OR REPLACE MACRO first_day_of_month
  () AS date_trunc('month', current_date)::DATE,
  (month_offset) AS date_trunc('month', current_date + month_offset * INTERVAL 1 month)::DATE,
  (month_offset, base_date) AS date_trunc('month', base_date + month_offset * INTERVAL 1 month)::DATE;

-- 月の最終日を返すマクロ
CREATE OR REPLACE MACRO last_day_of_month
  () AS last_day(current_date)::DATE,
  (month_offset) AS last_day(current_date + month_offset * INTERVAL 1 month)::DATE,
  (month_offset, base_date) AS last_day(base_date + month_offset * INTERVAL 1 month)::DATE;

-- 月の日数を返すマクロ
CREATE OR REPLACE MACRO days_of_month
  () AS date_part('day', last_day_of_month()),
  (month_offset) AS date_part('day', last_day_of_month(month_offset)),
  (month_offset, base_date) AS date_part('day', last_day_of_month(month_offset, base_date));

-- 試しに使ってみる

SELECT
  month_offset,
  base_date:          current_date + month_offset  * INTERVAL 1 month,
  first_day_of_month: first_day_of_month(month_offset),
  last_day_of_month:  last_day_of_month(month_offset),
  days_of_month:      days_of_month(month_offset)
FROM generate_series(-2, 2) as t(month_offset);
┌──────────────┬─────────────────────┬────────────────────┬───────────────────┬───────────────┐
│ month_offset │      base_date      │ first_day_of_month │ last_day_of_month │ days_of_month │
│    int64     │      timestampdatedate        │     int64     │
├──────────────┼─────────────────────┼────────────────────┼───────────────────┼───────────────┤
│           -22025-01-23 00:00:002025-01-012025-01-3131 │
│           -12025-02-23 00:00:002025-02-012025-02-2828 │
│            02025-03-23 00:00:002025-03-012025-03-3131 │
│            12025-04-23 00:00:002025-04-012025-04-3030 │
│            22025-05-23 00:00:002025-05-012025-05-3131 │
└──────────────┴─────────────────────┴────────────────────┴───────────────────┴───────────────┘
ログインするとコメントできます