日期函数解决的两件事
ClickHouse 的日期时间函数主要做两件事:把时间戳归一到桶(toStartOfHour、toStartOfDay、toStartOfInterval、date_trunc),以及做时间运算(dateDiff、date_add、直接与 INTERVAL 相加)。聚合类报表几乎每条语句都会用到其中至少一类。
口径与时区怎么定
时间列最好统一按 UTC 或固定时区存储,展示层再换算,函数 toTimeZone 可以做时区切换。周起点这类容易随版本与设置变化的口径,应该用 toStartOfInterval 显式指定。另一个常见误区是在过滤前对时间列做时区换算,这样会丢掉分区裁剪能力。
基本信息
| 取整 | toStartOfMinute/Hour/Day/Week/Month/Quarter/Year |
|---|---|
| 自定义区间 | toStartOfInterval(ts, INTERVAL 5 MINUTE) 或 date_trunc('minute', ts) |
| 运算 | now()、today()、dateDiff('day', a, b)、ts + INTERVAL 1 DAY |
| 时区 | toTimeZone(ts, 'Asia/Shanghai') |
| 适用版本 | 各主流版本均支持,个别函数为较新版本引入 |
示例
示例
SELECT
toStartOfInterval(ts, INTERVAL 5 MINUTE) AS bucket,
count() AS cnt
FROM metrics
WHERE ts >= now() - INTERVAL 1 HOUR
GROUP BY bucket
ORDER BY bucket;
-- 日期差值:注册到首次下单的间隔天数
SELECT dateDiff('day', reg_date, first_order_date) AS days_to_order
FROM users LIMIT 5;
-- 时区换算放展示层,过滤仍用原列
SELECT toTimeZone(ts, 'Asia/Shanghai') AS local_ts
FROM metrics LIMIT 5;典型使用场景
- 监控报表:按分钟或小时对齐时间轴做计数与均值
- 用户行为:计算注册到首单、下单到支付的时间差
- 分区路由:用日期函数生成分区键表达式
- 同比环比:把时间列归一成天或月再自关联
- 跨时区展示:存储用 UTC,报表按需换算
场景与推荐写法
| 场景 | 推荐写法 | 说明 |
|---|---|---|
| 固定粒度 | toStartOfHour/Day/Month | 语义明确,便于走分区与排序键 |
| 自定义粒度 | toStartOfInterval(ts, INTERVAL n UNIT) | 粒度与起点都能显式控制 |
| 通用截断 | date_trunc('minute', ts) | 与部分 SQL 方言习惯一致 |
| 求差值 | dateDiff('day', a, b) | 单位是字符串参数,拼写要准确 |
| 时区换算 | toTimeZone(ts, 'Zone') | 放在展示层,别放进过滤条件 |
时间列统一用 UTC 或固定时区存储、展示层再转换,是避免跨时区口径错乱最简单的方式;过滤条件尽量直接作用在原时区的时间列上。
toStartOfWeek 的周起点可能随版本与设置变化,关键口径要用 toStartOfInterval 显式指定,否则不同环境的报表会对不上;跨时区报表还要固定时间列本身的时区口径,避免同一份数据在不同客户端算出不同结果。