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


92

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

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-07和之间生成162个日期2004-08-16,这正是我想要的。该代码的问题在于,当两个日期来自不同年份时(例如,当我尝试使用2007-02-01和时),它将无法给出正确的答案2008-04-01

有更好的解决方案吗?


Answers:


173

可以在不转换为int或从int转换的情况下完成(而是改为timestamp的转换)

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

3
为什么date_trunc需要?
Idefixx

2
这只是演示。它消除了时间戳的时间部分的打印,在这种情况下,该时间部分始终为零。
beemtee '18

73

要生成一系列日期,这是最佳方法:

SELECT t.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列表中的返回设置功能:

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

AS关键字需要在最后的变体,Postgres的会曲解列别名day除外。而且我建议在Postgres 10之前使用该变体-至少不要在同一SELECT列表中具有多个set-returning函数:

(顺便说一句,最后一个变体通常最快的幅度很小。)

为什么timestamp [without time zone]

有许多重载的变体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)| 整数                    
generate_series(integer,integer)| 整数                    
generate_series(bigint,bigint,bigint)| 比金特                     
generate_series(bigint,bigint)| 比金特                     
generate_series(数字,数字,数字)| 数字                    
generate_series(数字,数字)| 数字                    
generate_series(没有时区的时间戳,没有时区的时间戳,间隔)| 没有时区的时间戳
generate_series(带时区的时间戳,带时区的时间戳,间隔)| 带时区的时间戳

numeric在Postgres 9.5中添加了变体。)相关的是粗体末尾并返回timestamp/的最后两个timestamptz

没有变异服用或返回date。需要显式转换才能返回date。带timestamp参数的调用可以直接解析为最佳变体,而无需遵循函数类型解析规则,也无需为输入进行额外的转换。

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

由于函数类型解析,我们仍然可以通过date。但这需要Postgres进行更多工作。有一个隐含的datetimestamp以及从一个datetimestamptz。会模棱两可,但在“日期/时间类型”中timestamptz“首选”。因此,在第4d步确定比赛

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

除了在函数类型解析中的额外工作之外,这还增加了额外的转换timestamptz-不仅增加了成本,而且还可能引入DST问题,在极少数情况下会导致意外结果。(顺便说一句,DST是一个愚蠢的概念,对此压力还不够。)

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

db <>在这里拨弄

有关:


7
更短的版本:SELECT generate_series(timestamp '2004-03-07', '2004-08-16', '1 day') :: DATE AS day;
瓦茨拉夫Kužel

t(day)语法表示什么?
仁当

@rendang:AS t(day)SELECT * FROM func() AS t(day)表和列别名中。AS在此上下文中,关键字是可选噪声。请参阅:stackoverflow.com/a/20230716/939860
Erwin Brandstetter

35

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

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

根据您的时区,这可能会返回意外的结果。我有这个问题。请改用时间戳记。设置会话时区'America / Sao_Paulo'SELECT d :: date FROM generate_series('2019-11-01':: date,'2019-11-03':: date,'1 day')d SELECT d :: date从generate_series('2019-11-01':: date,'2019-11-04':: date,'1 day')d
palhares

1

您也可以使用它。

select generate_series  ( '2012-12-31'::timestamp , '2018-10-31'::timestamp , '1 day'::interval) :: date 
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.