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

Posted

技术标签:

【中文标题】在 PostgreSQL 中生成两个日期之间的时间序列【英文标题】:Generating time series between two dates in PostgreSQL 【发布时间】:2012-12-16 07:31:40 【问题描述】:

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

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 时。

有没有更好的解决方案?

【问题讨论】:

Getting date list in a range in PostgreSQL 的可能重复项 【参考方案1】:

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

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

【讨论】:

为什么需要date_trunc 这只是演示。它消除了时间戳的时间部分的打印,在这种情况下始终为零。 date_trunc 不是必需的,因为您已经使用::date 将其强制为date 类型。无论有没有它,它都会产生相同的结果。 IIRC date_trunc() 在旧版本(8.4?)中是必需的。无论如何,它不会有害,您可以随时尝试省略演员表。【参考方案2】:

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

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 作为输入参数。相反,timestamp 是最佳选择。性能上的优势很小,但没有理由不拿下。而且您不必涉及 DST(夏令时)规则以及从 datetimestamp with time zone 的转换并返回。见下文。

等效的、不那么明确的短句法:

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

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

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

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

What is the expected behaviour for multiple set-returning functions in SELECT clause?

(除此之外,最后一个变体通常是最快的。)

为什么是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) |带时区的时间戳

numeric 变体是在 Postgres 9.5 中添加的。)相关的是最后两个粗体获取和返回timestamp/timestamptz

没有变体采用或返回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 增加了额外的强制转换——这不仅增加了成本,还可能在极少数情况下引入 DST 问题,导致意外结果。 (DST 是一个愚蠢的概念,顺便说一句,这一点怎么强调都不为过。)相关:

How do I generate a date series in PostgreSQL? How do I generate a time series in PostgreSQL?

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

db小提琴here

相关:

Is there a way to disable function overloading in Postgres Generate series of dates - using date type as input Postgres data type cast

【讨论】:

更短的版本:SELECT generate_series(timestamp '2004-03-07', '2004-08-16', '1 day') :: DATE AS day; t(day) 语法是什么意思? @rendang: AS t(day) in SELECT * FROM func() AS t(day) 是表和列的别名。 AS 关键字在这种情况下是可选的噪音。见:***.com/a/20230716/939860 你确定吗? “时间戳'2004-03-07'完全有效,顺便说一句。省略的时间部分默认为ISO格式的00:00。” @Seivan:绝对确定。无论语言环境或日期样式设置如何,ISO 8601 格式都是明确的。 (推荐用于所有日期/时间文字。)请参阅:postgresql.org/docs/current/interactive/…【参考方案3】:

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

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

【讨论】:

根据您的时区,这可能会返回意外结果。我有这个问题。请改用时间戳。 SET session TIME zone 'America/Sao_Paulo' SELECT d::date FROM generate_series('2019-11-01'::date, '2019-11-03'::date, '1 day') d SELECT d::date FROM generate_series('2019-11-01'::date, '2019-11-04'::date, '1 day') d【参考方案4】:

你也可以用这个。

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

【讨论】:

以上是关于在 PostgreSQL 中生成两个日期之间的时间序列的主要内容,如果未能解决你的问题,请参考以下文章

在PostgreSQL 和 Hive中生成日期序列

如何在Oracle中生成一周的第一天,一周的最后一天和两个日期之间的周数

在 Oracle 中生成具有 2 个日期之间时间间隔的行

在按周分组的 2 个日期之间在 MYSQL 中生成报告

在 gmt 中生成日期的所有分钟并获取其本地值

在 HIVE 中生成日期系列时,将有效日期限制为今天