如何在 PostgreSQL 中生成日期序列?

Eva*_*oll 12 postgresql timestamp timezone datetime date

如果您想生成时间序列,请参阅此问题

假设我想在两个日期之间生成一系列日期。我看到该功能generate_series仅提供

Function                                    Argument Type                         Return Type                                                               Description
generate_series(start, stop, step interval) timestamp or timestamp with time zone setof timestamp or setof timestamp with time zone (same as argument type) Generate a series of values, from start to stop with a step size of step
Run Code Online (Sandbox Code Playgroud)

那么我该怎么做呢?

Eva*_*oll 19

您可以generate_series为此使用,但一定要明确地将参数转换为“timestamp without time zone”,否则它们将默认为“timestamp with timezone”。generate_series两个输入的PostgreSQL 重载。

问题 timestamp with timezone

你可以在这里看到缺点。

SET timezone = 'America/Santiago';
SELECT generate_series(date '2016-08-15', date '2016-08-15', '1 day');    
SELECT generate_series(date '2016-08-14', date '2016-08-15', '1 day');
Run Code Online (Sandbox Code Playgroud)

以上两者都返回相同的天数。你可以在这里再次看到它。

SET timezone = 'America/Sao_Paulo';
SELECT generate_series(date '2016-10-16', date '2016-10-17', '1 day');
SELECT generate_series(date '2016-10-17', date '2016-10-17', '1 day');
Run Code Online (Sandbox Code Playgroud)

上面显示了一天的两个范围。

这种行为的原因是这些时区在午夜有他们的“夏令时边界,而不是在几个小时内更合理的时间”

那么“做对了”是什么样子的,

SELECT generate_series(
  timestamp without time zone '2016-10-16',
  timestamp without time zone '2016-10-17',
  '1 day'
);
Run Code Online (Sandbox Code Playgroud)

现在你可以投射到日期..

SELECT d::date
FROM generate_series(
  timestamp without time zone '2016-10-16',
  timestamp without time zone '2016-10-17',
  '1 day'
) AS gs(d);
Run Code Online (Sandbox Code Playgroud)

这个问题和答案的灵感来自与 RhodiumToad 在 IRC (irc://irc.freenode.net/#postgresql) 上的对话。他改变了我这个问题并提供了解决方案。

更新:两个潜在的修复

选项1: generate_series(date,date,interval)

玩玩,我发现我也许可以省去timestamp without time zone通过重载显式转换为generate_series(date,date,interval)

这是我的功能,

CREATE FUNCTION generate_series( t1 date, t2 date, i interval )
RETURNS setof date
AS $$
  SELECT d::date
  FROM generate_series(
    t1::timestamp without time zone,
    t2::timestamp without time zone,
    i
  )
    AS gs(d)
$$
LANGUAGE sql
IMMUTABLE;
Run Code Online (Sandbox Code Playgroud)

现在我可以重新运行上面的测试用例,它不再可疑。这两个都返回相同的东西,

SET timezone = 'America/Santiago';
SELECT d::date
FROM generate_series(date '2016-08-15', date '2016-08-15', '1 day')
  AS gs(d);

SELECT d::date
FROM generate_series(
  timestamp without time zone '2016-08-15',
  timestamp without time zone '2016-08-15',
  '1 day'
)
  AS gs(d);
Run Code Online (Sandbox Code Playgroud)

和这两个一样,

SELECT d::date
FROM generate_series(date '2016-08-14', date '2016-08-15', '1 day')
  AS gs(d);

SELECT d::date
FROM generate_series(
  timestamp without time zone '2016-08-14',
  timestamp without time zone '2016-08-15',
  '1 day'
)
  AS gs(d);
Run Code Online (Sandbox Code Playgroud)

选项 2: generate_series(date,date,int)

另一种选择是创建一个新函数,generate_series(date,date,int)但是由于这里提到的原因,您不能同时拥有这两个函数。所以选择其中之一,

generate_series(date,date,interval)
generate_series(date,date,int)
Run Code Online (Sandbox Code Playgroud)

如果你想要第二个选项,试试这个:

CREATE FUNCTION generate_series( t1 date, t2 date, i int )
RETURNS setof date
AS $$
  SELECT d::date
  FROM generate_series(
    t1::timestamp without time zone,
    t2::timestamp without time zone,
    i * interval '1 day'
  )
    AS gs(d)
$$
LANGUAGE sql
IMMUTABLE;
Run Code Online (Sandbox Code Playgroud)

注意事项

随着irc的审查,这些想法存在一些问题,

< johto> generate_series(date, date, unknown)今天已经开始工作了。当您不使用 int 版本(例如generate_series(date, date, '1 day'))彻底打破它时,您将返回类型从 timestamptz 更改为 date。(date, date, interval)会打破更少的情况,但你仍然会改变输出类型。((date, date, '1 hour') 目前“工作正常”应该发生什么也不清楚 )

  • 我昨天在 SO 上写了一个密切相关的答案,其中包含有关函数类型解析的详细信息:/sf/answers/3254991141/。我刚才看到了这个,并添加了一个链接,因为您的 DST 演示很有洞察力。 (2认同)