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

时间:2013-01-01 19:23:44

标签: postgresql date time-series postgresql-9.1 generate-series

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

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时,它无法给出正确的答案。

有更好的解决方案吗?

4 个答案:

答案 0 :(得分:113)

可以在不转换为/从int转换的情况下完成(但是转换为/来自时间戳)

SELECT date_trunc('day', dd):: date
FROM generate_series
        ( '2007-02-01'::timestamp 
        , '2008-04-01'::timestamp
        , '1 day'::interval) dd
        ;

答案 1 :(得分:41)

这应该是 最佳 方式:

SELECT day::date 
FROM   generate_series(timestamp '2004-03-07'
                     , timestamp '2004-08-16'
                     , interval  '1 day') AS t(day);
  • 不需要额外的date_trunc()。对dateday::date)的强制转换是隐含的。

  • 但将日期文字转换为date作为输入参数也没有意义。相反, timestamp是最佳选择。性能的优势很小,但没有理由不采取它。并且您不必毫不涉及DST(夏令时)规则以及从datetimestamp with time zone并返回的转换。见下文。

等效的短语法:

SELECT day::date 
FROM   generate_series(timestamp '2004-03-07', '2004-08-16', '1 day') day;

或使用SELECT列表中的set-returns函数:

SELECT generate_series(timestamp '2004-03-07', '2004-08-16', '1 day')::date AS day;

最后一个版本中AS关键字必需,否则Postgres会误解列别名day。而且我会在Postgres 10之前建议该变体 - 至少在同一个SELECT列表中没有多个set-returns函数:

为什么?

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

SELECT oid::regprocedure   AS function_signature
     , prorettype::regtype AS return_type
FROM   pg_proc
where  proname = 'generate_series';
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

(Postgres 9.5添加了numeric个变体。)相关的变体是最后两个以粗体显示并且返回timestamp / timestamptz。< / p>

正如您所看到的,没有变体采取或返回date 。返回date需要显式强制转换。传递timestamp直接解析为最佳变体,而不会降低到函数类型解析规则,也无需为输入添加额外的强制转换。

timestamp '2004-03-07'完全有效,顺便说一下。省略的时间部分默认为00:00,具有ISO格式。

感谢function type resolution,我们仍然可以通过date。但这需要Postgres的更多工作。从datetimestamp以及从datetimestamptz都有一个 隐式强制强制。不明确,但“日期/时间类型”中timestamptz “首选”。所以match is decided at step 4d.

  

贯穿所有候选人并保留那些接受首选类型的候选人   (输入数据类型的类型类别)在大多数位置   将需要类型转换。如果不接受,请保留所有候选人   首选类型。如果只剩下一名候选人,请使用它;否则继续   到下一步。

除了函数类型解析中的额外工作外,这还为timestamptz添加了额外的强制转换。对timestamptz的强制转换不仅增加了成本,而且还会引入DST问题,在极少数情况下会导致意外结果。 (DST是一个愚蠢的概念,顺便说一句,不能强调这一点。)相关:

我将演示添加到小提琴中,显示更昂贵的查询计划:

dbfiddle here

相关:

答案 2 :(得分:32)

您可以直接生成日期系列。无需使用整数或时间戳:

select date::date 
from generate_series(
  '2004-03-07'::date,
  '2004-08-16'::date,
  '1 day'::interval
) date;

答案 3 :(得分:0)

您可以使用like

select generate_series('2012-12-31':: timestamp,'2018-10-31':: timestamp,'1 day':: interval)::日期