Создание временных рядов между двумя датами в 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
Как мне сделать эту работу за месяц? Всякий раз, когда я использую в течение месяца, это не решает для високосных лет и после февраля, это становится проблематичным