【发布时间】:2019-04-13 20:18:40
【问题描述】:
使用 postgresql 版本 > 10,我在使用内置 generate_series 函数生成日期系列时遇到了问题。从本质上讲,它不正确地符合day of the month。
我有许多不同的频率(由用户提供)需要在给定的开始日期和结束日期之间计算。开始日期可以是任何日期,因此可以是一个月中的任何一天。当频率(例如 monthly)与 2018-01-31 或 2018-01-30 的开始日期相结合时,这会产生问题,如下面的输出所示。
我创建了一个解决方案并想在此处发布它以供其他人使用,因为我找不到任何其他解决方案。
但是,经过一些测试,我发现我的解决方案与内置的 generate_series 在(荒谬的)大日期范围内使用时具有不同的性能。有没有人知道如何改进?
TL;DR:如果可能避免循环,因为它们会影响性能,滚动到底部以改进实现。
内置输出
select generate_series(date '2018-01-31',
date '2018-05-31',
interval '1 month')::date
as frequency;
生成:
frequency
------------
2018-01-31
2018-02-28
2018-03-28
2018-04-28
2018-05-28
从输出中可以看出,月份中的某一天没有得到尊重,并被截断为沿途遇到的最小天数,在本例中为:28 due to the month of februari。
预期输出
由于这个问题,我创建了一个自定义函数:
create or replace function generate_date_series(
starts_on date,
ends_on date,
frequency interval)
returns setof date as $$
declare
interval_on date := starts_on;
count int := 1;
begin
while interval_on <= ends_on loop
return next interval_on;
interval_on := starts_on + (count * frequency);
count := count + 1;
end loop;
return;
end;
$$ language plpgsql immutable;
select generate_date_series(date '2018-01-31',
date '2018-05-31',
interval '1 month')
as frequency;
生成:
frequency
------------
2018-01-31
2018-02-28
2018-03-31
2018-04-30
2018-05-31
性能对比
无论提供什么日期范围,内置的generate_series 的平均性能2ms:
select generate_series(date '1900-01-01',
date '10000-5-31',
interval '1 month')::date
as frequency;
而自定义函数generate_date_series 的平均性能为120ms:
select generate_date_series(date '1900-01-01',
date '10000-5-31',
interval '1 month')::date
as frequency;
问题
实际上,这样的范围永远不会出现,因此这不是问题。对于大多数查询,自定义 generate_date_series 将获得相同的性能。不过,我确实想知道是什么导致了这种差异。
无论提供什么范围,内置函数都能获得平均2ms的恒定性能有什么原因吗?
有没有更好的方法来实现generate_date_series,它的性能和内置的generate_series一样好?
改进的无循环实现
(源自@eurotrash 的回答)
create or replace function generate_date_series(
starts_on date,
ends_on date,
frequency interval)
returns setof date as $$
select (starts_on + (frequency * count))::date
from (
select (row_number() over ()) - 1 as count
from generate_series(starts_on, ends_on, frequency)
) series
$$ language sql immutable;
通过改进的实现,generate_date_series 函数的平均性能为 45 毫秒:
select generate_date_series(date '1900-01-01',
date '10000-5-31',
interval '1 month')::date
as frequency;
@eurotrash 提供的实现平均为我提供 80 毫秒,我认为这是由于两次调用 generate_series 函数所致。
【问题讨论】:
标签: sql postgresql dateinterval generate-series