Створення часових рядів між двома датами в PostgreSQL


92

У мене є такий запит, який чудово генерує серію дат між 2 заданими датами:

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.

Чи є краще рішення?


Відповіді:


175

Можна зробити без перетворення в / з int (але замість цього в / з мітки часу)

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

3
навіщо date_truncпотрібен?
Idefixx

2
Це просто презентація. Це виключає друк часової частини мітки часу, яка в цьому випадку є нулями.
beemtee

73

Щоб створити серію дат це оптимальний спосіб:

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як вхідний параметр. Au contraire timestamp- найкращий вибір . Перевага у виконанні невелика, але немає причин не брати її. І вам не потрібно зайво застосовувати правила переходу на літній час (літній час) у поєднанні з перетворенням з 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';
підпис_функції | return_type                
: ------------------------------------------------- ------------------------------- | : --------------------------
create_series (ціле число, ціле число, ціле число) | ціле число                    
create_series (ціле число, ціле число) | ціле число                    
create_series (bigint, bigint, bigint) | bigint                     
create_series (bigint, bigint) | bigint                     
create_series (числовий, числовий, числовий) | числовий                    
create_series (числові, числові) | числовий                    
generated_series (відмітка часу без часового поясу, відмітка часу без часового поясу, інтервал) | позначка часу без часового поясу
generated_series (позначка часу з часовим поясом, позначка часу з часовим поясом, інтервал) | позначка часу з часовим поясом

( numericваріанти були додані до Postgres 9.5.) Відповідними є два останні жирним шрифтом взяття та повернення timestamp/ timestamptz.

Немає варіанту взяти або повернутиdate . Для повернення потрібен явний акторський склад date. Виклик з timestampаргументами переходить до найкращого варіанту безпосередньо, не опускаючись до правил роздільної здатності типу функції та без додаткового приведення для введення.

timestamp '2004-03-07'цілком дійсний, до речі. Пропущена частина часу за замовчуванням 00:00має формат ISO.

Завдяки роздільній здатності типу функції ми все ще можемо пройти date. Але для цього потрібна додаткова робота Postgres. Існує неявний привід від dateдо timestamp, а також один від dateдо timestamptz. Було б неоднозначно, але timestamptzє "кращим" серед "типів дати / часу". Тож матч вирішується на кроці 4г. :

Пройдіть усіх кандидатів і залиште тих, хто приймає бажані типи (категорії типів вхідних типів даних), на більшості позицій, де потрібно перетворення типу. Зберігайте всіх кандидатів, якщо жоден не приймає бажані типи. Якщо залишився лише один кандидат, використовуйте його; в іншому випадку перейдіть до наступного кроку.

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

Я додав демонстрації до скрипки, що показує більш дорогий план запитів:

db <> скрипка тут

Пов’язані:


7
Ще коротша версія:SELECT generate_series(timestamp '2004-03-07', '2004-08-16', '1 day') :: DATE AS day;
Václav Kužel

Що означає синтаксис t (день)?
rendang

@rendang: AS t(day)в SELECT * FROM func() AS t(day)- псевдонім таблиці та стовпця. У ASцьому контексті ключовим словом є необов’язковий шум. Див .: stackoverflow.com/a/20230716/939860
Ервін Брандштеттер

35

Ви можете генерувати серії безпосередньо з датами. Не потрібно використовувати ints або мітки часу:

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

Залежно від вашого часового поясу, це може повернути несподіваний результат. У мене була ця проблема. Натомість використовуйте позначку часу. ВСТАНОВИТИ ЧАСОВУ зону сесії 'America / Sao_Paulo' SELECT d :: date FROM generated_series ('2019-11-01' :: date, '2019-11-03' :: date, '1 day') d SELECT d :: date FROM generated_series ('2019-11-01' :: date, '2019-11-04' :: date, '1 day') d
palhares

1

Ви також можете використовувати це.

select generate_series  ( '2012-12-31'::timestamp , '2018-10-31'::timestamp , '1 day'::interval) :: date 
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.