Создание временных рядов между двумя датами в PostgreSQL

У меня есть такой запрос, который красиво генерирует серию дат между двумя заданными датами:

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

Он генерирует 162 даты между 2004-03-07 и 2004-08-16, и это то, что я хочу. Проблема с этим кодом заключается в том, что он не даст правильного ответа, когда две даты относятся к разным годам, например, когда я пытаюсь выполнить 2007-02-01 и 2008-04-01.

Есть ли лучшее решение?

Ответ 1

Может быть выполнено без преобразования в/из int (но вместо /timestamp )

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

Ответ 2

Для генерации серии дат dates это оптимальный способ :

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:

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):

SELECT oid::regprocedure   AS function_signature
     , prorettype::regtype AS return_type
FROM   pg_proc
where  proname = 'generate_series';
function_signature                                                                | return_type                
:-------------------------------------------------------------------------------- | :--------------------------
generate_series(integer,integer,integer)                                          | integer                    
generate_series(integer,integer)                                                  | integer                    
generate_series(bigint,bigint,bigint)                                             | bigint                     
generate_series(bigint,bigint)                                                    | bigint                     
generate_series(numeric,numeric,numeric)                                          | numeric                    
generate_series(numeric,numeric)                                                  | numeric                    
generate_series(timestamp without time zone,timestamp without time zone,interval) | timestamp without time zone
generate_series(timestamp with time zone,timestamp with time zone,interval)       | timestamp with time zone

(Варианты 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 является "предпочтительным" среди "типов даты/времени". Таким образом, соответствие определяется на шаге 4d.:

Пройдите через всех кандидатов и оставьте тех, которые принимают предпочтительные типы (из типа типа входных данных) в большинстве позиций, где потребуется преобразование типов. Оставьте всех кандидатов, если никто не примет предпочтительные типы. Если остается только один кандидат, используйте его; еще продолжить к следующему шагу.

В дополнение к дополнительной работе по разрешению типов функций это добавляет дополнительное приведение к timestamptz - что не только увеличивает стоимость, но также может создавать проблемы с DST, приводящие к неожиданным результатам в редких случаях. (DST - это дебильная концепция, кстати, не могу этого подчеркнуть.) Похожие:

Я добавил демо в скрипку, показывающую более дорогой план запроса:

db & lt;> скрипка здесь

Связанный:

Ответ 3

Вы можете генерировать серию напрямую с датами. Нет необходимости использовать ints или метки времени:

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

Ответ 4

Вы также можете использовать это.

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

Ответ 5

Как мне сделать эту работу за месяц? Всякий раз, когда я использую в течение месяца, это не решает для високосных лет и после февраля, это становится проблематичным