在 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-07
和 2004-08-16
之间生成 162 个日期,这就是我想要的。这段代码的问题在于,当两个日期来自不同年份时,它不会给出正确的答案,例如当我尝试2007-02-01
和2008-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(夏令时)规则以及从 date
到 timestamp 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
列表中没有一个以上的集合返回函数:
(除此之外,最后一个变体通常是最快的。)
为什么是timestamp [without time zone]
?
generate_series()
有许多重载变体。目前(Postgres 11):
函数签名 |返回类型 :------------------------------------------------ ------------------------------------------- | :---------------------------- generate_series(整数,整数,整数) |整数 生成系列(整数,整数)|整数 生成系列(大整数,大整数,大整数) |大整数 生成系列(大整数,大整数) |大整数 generate_series(数字,数字,数字) |数字 生成系列(数字,数字)|数字 generate_series(不带时区的时间戳,不带时区的时间戳,间隔) |没有时区的时间戳 generate_series(timestamp with time zone,timestamp with time zone,interval) |带时区的时间戳SELECT oid::regprocedure AS function_signature , prorettype::regtype AS return_type FROM pg_proc where proname = 'generate_series';
(numeric
变体是在 Postgres 9.5 中添加的。)相关的是最后两个粗体获取和返回timestamp
/timestamptz
。
没有变体采用或返回date
。需要显式转换才能返回 date
。带有timestamp
参数的调用直接解析为最佳变体,无需降级到函数类型解析规则,也无需对输入进行额外的强制转换。
timestamp '2004-03-07'
完全有效,顺便说一句。省略的时间部分默认为00:00
,ISO格式。
感谢function type resolution,我们仍然可以通过date
。但这需要 Postgres 做更多的工作。从date
到timestamp
以及从date
到timestamptz
有一个隐式演员表。会模棱两可,但timestamptz
在“日期/时间类型”中是“首选”。所以match is decided at step 4d.:
遍历所有候选人并保留那些接受首选类型的候选人 (输入数据类型的类型类别)在大多数位置 将需要类型转换。如果没有接受,则保留所有候选人 首选类型。如果只剩下一个候选人,请使用它;否则继续 到下一步。
除了函数类型解析方面的额外工作之外,这还为timestamptz
增加了额外的强制转换——这不仅增加了成本,还可能在极少数情况下引入 DST 问题,导致意外结果。 (DST 是一个愚蠢的概念,顺便说一句,这一点怎么强调都不为过。)相关:
我在小提琴中添加了演示,展示了更昂贵的查询计划:
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 中生成两个日期之间的时间序列的主要内容,如果未能解决你的问题,请参考以下文章