114

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

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-012008-04-01.

有更好的解决方案吗?

4

4 回答 4

213

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

SELECT date_trunc('day', dd):: date
FROM generate_series
        ( '2007-02-01'::timestamp 
        , '2008-04-01'::timestamp
        , '1 day'::interval) dd
        ;
于 2013-01-01T19:40:39.317 回答
93

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

SELECT t.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为输入参数也没有意义。Au contraire,timestamp是最好的选择。性能上的优势很小,但没有理由不拿下。而且您不必涉及 DST(夏令时)规则以及来回date转换。timestamp with time zone见下文。

等效的,不太明确的简短语法:

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

或者使用列表中的 set-returning 函数SELECT

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

在最后一个变体中需要AS关键字,否则 Postgres 会误解列别名。而且我建议在 Postgres 10 之前使用该变体 - 至少在同一个列表中没有一个以上的 set-returning 函数:daySELECT

(除此之外,最后一个变体通常以微弱的优势最快。)

为什么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';
函数签名 | 返回类型                
:------------------------------------------------ ------------------------------------------- | :----------------------------
generate_series(整数,整数,整数) | 整数                    
生成系列(整数,整数)| 整数                    
生成系列(大整数,大整数,大整数) | 大整数                     
生成系列(大整数,大整数) | 大整数                     
generate_series(数字,数字,数字) | 数字                    
生成系列(数字,数字)| 数字                    
generate_series(时间戳不带时区,时间戳不带时区,间隔) | 没有时区的时间戳
generate_series(timestamp with time zone,timestamp with time zone,interval) | 带时区的时间戳

numericPostgres 9.5 添加了变体。)相关的是最后两个以粗体取和返回timestamp/ timestamptz

没有变体采取或返回date。需要显式强制转换才能返回date。带timestamp参数的调用直接解析为最佳变体,而无需下降到函数类型解析规则,也无需对输入进行额外的强制转换。

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

感谢函数类型解析,我们仍然可以通过date. 但这需要 Postgres 做更多的工作。有一个隐式转换from datetotimestamp和一个 from dateto timestamptz。会模棱两可,但在“日期/时间类型”中timestamptz“首选”。所以比赛是在步骤4d 决定的。

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

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

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

db<>在这里摆弄

有关的:

于 2017-09-30T05:33:11.260 回答
38

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

select date::date 
from generate_series(
  '2004-03-07'::date,
  '2004-08-16'::date,
  '1 day'::interval
) date;
于 2015-02-25T17:06:08.480 回答
2

你也可以使用这个。

select generate_series  ( '2012-12-31'::timestamp , '2018-10-31'::timestamp , '1 day'::interval) :: date 
于 2018-10-31T11:52:35.910 回答