生成PostgreSQL中两个日期之间的时间序列

Generating time series between two dates in PostgreSQL

我有一个这样的查询,它可以很好地在两个给定日期之间生成一系列日期:

1
2
3
SELECT DATE '2004-03-07' + j - i AS AllDate
FROM generate_series(0, EXTRACT(doy FROM DATE '2004-03-07')::INT - 1) AS i,
     generate_series(0, EXTRACT(doy FROM DATE '2004-08-16')::INT - 1) AS j

它在2004-03-072004-08-16之间产生162个日期,这是我想要的。这段代码的问题在于,当这两个日期来自不同的年份时,它不会给出正确的答案,例如,当我尝试2007-02-012008-04-01时。

有更好的解决方案吗?


可以在不转换为int/from int的情况下完成(但改为转换为to/from timestamp)

1
2
3
4
5
6
SELECT date_trunc('day', dd):: DATE
FROM generate_series
        ( '2007-02-01'::TIMESTAMP
        , '2008-04-01'::TIMESTAMP
        , '1 day'::INTERVAL) dd
        ;


这应该是最佳方式:

1
2
3
4
SELECT DAY::DATE
FROM   generate_series(TIMESTAMP '2004-03-07'
                     , TIMESTAMP '2004-08-16'
                     , INTERVAL  '1 day') AS t(DAY);
  • 不需要额外的date_trunc()。对date(day::date)的强制转换是含蓄的。

  • 但也没有必要将日期文本作为输入参数强制转换到date中。相反,江户十一〔四〕是最好的选择。在性能上的优势很小,但没有理由不接受它。而且,您不必涉及DST(夏令时)规则以及从datetimestamp with time zone和后面的转换。见下文。

等效短语法:

1
2
SELECT DAY::DATE
FROM   generate_series(TIMESTAMP '2004-03-07', '2004-08-16', '1 day') DAY;

或者使用SELECT列表中的设置返回功能:

1
SELECT generate_series(TIMESTAMP '2004-03-07', '2004-08-16', '1 day')::DATE AS DAY;

最后一个变量中需要AS关键字,否则postgres会误解列别名day。我不建议在postgres 10之前的变体-至少在同一个SELECT列表中不具有多个集合返回函数:

  • select子句中多个返回函数的集合的预期行为是什么?

为什么?

generate_series()有许多过载的变体。目前(Postgres 11):

1
2
3
4
SELECT oid::regprocedure   AS function_signature
     , prorettype::regtype AS return_type
FROM   pg_proc
WHERE  proname = 'generate_series';
1
2
3
4
5
6
7
8
9
10
function_signature                                                                | return_type                
:-------------------------------------------------------------------------------- | :--------------------------
generate_series(INTEGER,INTEGER,INTEGER)                                          | INTEGER                    
generate_series(INTEGER,INTEGER)                                                  | INTEGER                    
generate_series(BIGINT,BIGINT,BIGINT)                                             | BIGINT                    
generate_series(BIGINT,BIGINT)                                                    | BIGINT                    
generate_series(NUMERIC,NUMERIC,NUMERIC)                                          | NUMERIC                    
generate_series(NUMERIC,NUMERIC)                                                  | NUMERIC                    
generate_series(TIMESTAMP WITHOUT TIME zone,TIMESTAMP WITHOUT TIME zone,INTERVAL) | TIMESTAMP WITHOUT TIME zone
generate_series(TIMESTAMP WITH TIME zone,TIMESTAMP WITH TIME zone,INTERVAL)       | TIMESTAMP WITH TIME zone

(numeric变种加上postgres 9.5。)相关变种是最后两个粗体的timestamp/timestamptz变种。

如您所见,不存在接受或返回date的变体。返回date需要显式强制转换。传递timestamp将直接解析为最佳变量,而不下降为函数类型解析规则,也不为输入添加额外的强制转换。

btw,timestamp '2004-03-07'是完全有效的。省略的时间部分默认为iso格式的00:00

由于函数类型解析,我们仍然可以通过date。但这需要Postgres做更多的工作。存在从datetimestamp以及从datetimestamptz的隐式强制转换。可能不明确,但在"日期/时间类型"中,timestamptz是"首选"。所以比赛在第4d步决定:

Run through all candidates and keep those that accept preferred types
(of the input data type's type category) at the most positions where
type conversion will be required. Keep all candidates if none accept
preferred types. If only one candidate remains, use it; else continue
to the next step.

除了函数类型解析中的额外工作之外,这还为timestamptz添加了额外的强制转换。铸造到timestamptz不仅增加了成本,而且还可能引入DST的问题,导致罕见情况下的意外结果。(DST是一个病态概念,顺便说一句,不能强调这一点。)相关:

  • 如何在PostgreSQL中生成日期序列?
  • 如何在PostgreSQL中生成时间序列?

我在小提琴上增加了演示,展示了更昂贵的查询计划:

此处

相关:

  • 有没有办法在Postgres中禁用函数重载
  • 生成一系列日期-使用日期类型作为输入
  • Postgres数据类型转换


可以直接用日期生成序列。无需使用ints或时间戳:

1
2
3
4
5
6
SELECT DATE::DATE
FROM generate_series(
  '2004-03-07'::DATE,
  '2004-08-16'::DATE,
  '1 day'::INTERVAL
) DATE;

你可以用like

选择Generate_series("2012-12-31"::timestamp,"2018-10-31"::timestamp,"1 day"::interval)::date