Используя версию 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(
startsOn date,
endsOn date,
frequency interval)
returns setof date as $$
declare
intervalOn date := startsOn;
count int := 1;
begin
while intervalOn <= endsOn loop
return next intervalOn;
intervalOn := startsOn + (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
имеет производительность 2 мс в среднем для:
select generate_series(date '1900-01-01',
date '10000-5-31',
interval '1 month')::date
as frequency;
в то время как пользовательская функция generate_date_series
имеет производительность 120 мс в среднем для:
select generate_date_series(date '1900-01-01',
date '10000-5-31',
interval '1 month')::date
as frequency;
Вопрос
На самом деле, такие диапазоны никогда не появятся, и поэтому это не проблема. Для большинства запросов пользовательский generate_date_series
будет иметь ту же производительность. Хотя мне интересно, что вызывает разницу.
Существует ли причина, по которой встроенная функция может достигать постоянной производительности в среднем 2 мс независимо от того, какой диапазон предоставляется?
Есть ли лучший способ реализовать generate_date_series
, который работает так же хорошо, как встроенный generate_series
?
Улучшена реализация без циклов
(получено из ответа @eurotrash)
create or replace function generate_date_series(startsOn date, endsOn date, frequency interval)
returns setof date as $$
select (startsOn + (frequency * count))::date
from (
select (row_number() over ()) - 1 as count
from generate_series(startsOn, endsOn, 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
.