PostgreSQL生成日期序列时,能否替代CASE WHEN简化查询?
简化PostgreSQL日期序列生成的CASE WHEN逻辑
我需要生成可适配不同周期(月/周/日)的PostgreSQL日期序列,核心规则是:
- 首个周期的
date字段使用generate_series的输入起始日期 - 后续周期的
date字段取对应周期的起始日(月序列取月初、周序列取周初、日序列取当日)
目前通过CASE WHEN实现该逻辑,以下是三种周期的实现示例及正确结果:
月序列示例
SELECT date::DATE AS sequence_value, CASE WHEN date = '2022-01-16' THEN date::DATE ELSE date_trunc('MONTH', date)::DATE END AS date, (DATE_TRUNC('MONTH', date) + INTERVAL '1 MONTH')::DATE AS date_end, to_char(date, 'MONTH') AS name FROM GENERATE_SERIES('2022-01-16', '2023-01-02', INTERVAL '1 MONTH') AS date
结果:
| sequence_value | date | date_end | name |
|---|---|---|---|
| 2022-01-16 | 2022-01-16 | 2022-02-01 | JANUARY |
| 2022-02-16 | 2022-02-01 | 2022-03-01 | FEBRUARY |
| 2022-03-16 | 2022-03-01 | 2022-04-01 | MARCH |
| 2022-04-16 | 2022-04-01 | 2022-05-01 | APRIL |
周序列示例
SELECT date::DATE AS sequence_value, CASE WHEN date = '2022-01-16' THEN date::DATE ELSE date_trunc('WEEK', date)::DATE END AS date, (DATE_TRUNC('WEEK', date) + INTERVAL '1 WEEK')::DATE AS date_end, to_char(date, 'MM-DD') AS name FROM GENERATE_SERIES('2022-01-16', '2023-01-02', INTERVAL '1 WEEK') AS date
结果:
| sequence_value | date | date_end | name |
|---|---|---|---|
| 2022-01-16 | 2022-01-16 | 2022-01-17 | 01-16 |
| 2022-01-23 | 2022-01-17 | 2022-01-24 | 01-23 |
| 2022-01-30 | 2022-01-24 | 2022-01-31 | 01-30 |
| 2022-02-06 | 2022-01-31 | 2022-02-07 | 02-06 |
日序列示例
SELECT date::DATE AS sequence_value, CASE WHEN date = '2022-01-16' THEN date::DATE ELSE date_trunc('DAY', date)::DATE END AS date, (DATE_TRUNC('DAY', date) + INTERVAL '1 DAY')::DATE AS date_end, to_char(date, 'MM-DD') AS name FROM GENERATE_SERIES('2022-01-16', '2023-01-02', INTERVAL '1 DAY') AS date
结果:
| sequence_value | date | date_end | name |
|---|---|---|---|
| 2022-01-16 | 2022-01-16 | 2022-01-17 | 01-16 |
| 2022-01-17 | 2022-01-17 | 2022-01-18 | 01-17 |
| 2022-01-18 | 2022-01-18 | 2022-01-19 | 01-18 |
| 2022-01-19 | 2022-01-19 | 2022-01-20 | 01-19 |
目前日序列的CASE WHEN可以省略,但月序列必须保留以避免将16日作为月初。想请教有没有更简洁高效的写法替代CASE WHEN?
优化方案
方案1:用ROW_NUMBER()判断首行
利用generate_series生成的序列天然有序的特性,通过行号判断是否为第一条记录,无需硬编码起始日期,更适配参数化场景:
月序列优化版:
SELECT date::DATE AS sequence_value, CASE WHEN row_number() OVER () = 1 THEN date::DATE ELSE date_trunc('MONTH', date)::DATE END AS date, (DATE_TRUNC('MONTH', date) + INTERVAL '1 MONTH')::DATE AS date_end, to_char(date, 'MONTH') AS name FROM GENERATE_SERIES('2022-01-16', '2023-01-02', INTERVAL '1 MONTH') AS date
这个写法的优势是:起始日期如果是后端传入的参数,不需要在CASE WHEN里重复写,逻辑更通用,不易出错。
方案2:CTE定义参数统一管理
如果需要多次复用起始日期参数,可以用CTE将参数抽离,避免重复硬编码:
月序列优化版:
WITH params AS ( SELECT '2022-01-16'::DATE AS start_date ) SELECT gs.date::DATE AS sequence_value, CASE WHEN gs.date = params.start_date THEN gs.date::DATE ELSE date_trunc('MONTH', gs.date)::DATE END AS date, (DATE_TRUNC('MONTH', gs.date) + INTERVAL '1 MONTH')::DATE AS date_end, to_char(gs.date, 'MONTH') AS name FROM GENERATE_SERIES(params.start_date, '2023-01-02', INTERVAL '1 MONTH') AS gs(date) CROSS JOIN params
这种写法更便于维护,参数只需要在CTE中修改一次即可。
日序列简化
日序列中date_trunc('DAY', date)的结果和原日期完全一致,所以可以直接去掉CASE WHEN,简化为:
SELECT date::DATE AS sequence_value, date_trunc('DAY', date)::DATE AS date, (DATE_TRUNC('DAY', date) + INTERVAL '1 DAY')::DATE AS date_end, to_char(date, 'MM-DD') AS name FROM GENERATE_SERIES('2022-01-16', '2023-01-02', INTERVAL '1 DAY') AS date
内容的提问来源于stack exchange,提问作者Simonas Petkevičius
相关产品推荐
相关产品推荐

