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

问题描述 投票:61回答:4

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

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

有更好的解决方案吗?

postgresql date time-series postgresql-9.1 generate-series
4个回答
115
投票

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

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

42
投票

这应该是最佳方式:

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函数:

Why?

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

numeric变种与Postgres 9.5一起添加。)相关的是大胆采取和返回timestamp / timestamptz的最后两个。

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

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

感谢function type resolution,我们仍然可以通过date。但这需要Postgres的更多工作。从datetimestamp以及从datetimestamptz的隐含演员。将是模棱两可的,但timestamptz在“日期/时间类型”中是“首选”。所以match is decided at step 4d.

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

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

我在演示更昂贵的查询计划的小提琴中添加了演示:

dbfiddle here

有关:


32
投票

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

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

0
投票

你可以使用喜欢

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

© www.soinside.com 2019 - 2024. All rights reserved.