Использование переменной period в интервале в Postgres

#postgresql #variables #intervals #period

#postgresql #переменные #интервалы #период

Вопрос:

У меня есть отношение, которое поддерживает ежемесячные исторические данные. Эти данные добавляются в таблицу в последний день каждого месяца. Затем можно вызвать службу, которую я пишу, указав месяц и количество месяцев, предшествующих для получения исторических данных. Я делаю это, создавая переменные StartDate и EndDate, а затем возвращая данные между ними. Проблема, с которой я сталкиваюсь, заключается в том, что StartDate — это переменное число месяцев до EndDate, и я не могу понять, как использовать переменный период в интервале.

Вот что у меня есть:

     DECLARE
      endDate   TIMESTAMP := (DATE_TRUNC('MONTH',$2)   INTERVAL '1 MONTH') - INTERVAL '1 DAY';
      startDate TIMESTAMP := endDate - INTERVAL $3 'MONTH';
  

Я знаю, что строка для StartDate неверна. Как это правильно сделать?

Ответ №1:

Используйте эту строку:

 startDate TIMESTAMP := endDate - ($3 || ' MONTH')::INTERVAL;
  

и обратите внимание на пробел перед MONTH .
В принципе: вы создаете строку с like 4 MONTH и добавляете ее ::type в соответствующий интервал.

Редактировать: я нашел другое решение: вы можете вычислять с interval помощью этого:

 startDate TIMESTAMP := endDate - $3 * INTERVAL '1 MONTH';
  

Для меня это выглядит немного приятнее.

Комментарии:

1. Или: endDate - '1 mon'::interval * $3 (меньше приведения и объединения)

2. @A.H. Круто 🙂 Не забудьте @reply в комментариях , иначе я могу пропустить ваш ответ.

3. Умножение на интервал — очень хорошее решение!

Ответ №2:

Этот код не имеет прямого отношения к вашей ситуации, но он иллюстрирует, как использовать переменные в интервальной арифметике. Имя моей таблицы — «календарь».

 CREATE OR REPLACE FUNCTION test_param(num_months integer)
  RETURNS SETOF calendar AS
$BODY$

    select * from calendar
    where cal_date <= '2008-12-31 00:00:00'
    and cal_date > date '2008-12-31' - ($1 || ' month')::interval;

$BODY$
  LANGUAGE sql VOLATILE
  COST 100
  ROWS 1000;
  

Ответ №3:

Наиболее удобный способ, который я нашел для передачи переменного периода времени в Postgres, аналогичен ответу A.H.: путем умножения на целое число. Но это можно сделать и без приведения.

Пример Python (с использованием sqlalchemy и pandas):

 import pandas as pd
import sqlalchemy as sa

connection = sa.create_engine(connection_string)

df = pd.read_sql(
   sa.text('''
       select * from events
       where
       event_date between now() - (interval '1 day' * :ndays) and now()
       limit 100;
'''),
   connection,
   params={'ndays': 100}
)
  

Количество дней (ndays) передается как целое число из Python, поэтому непреднамеренные последствия менее вероятны.

Ответ №4:

Мой подход такой.. Это дает мне возможность установить конкретную дату или относительный диапазон.

 create or replace function search_data(_time_from timestamptz default null, _last_interval text default null)
    returns setof journal
    language plpgsql as
$$
begin
    return query
        select *
        from journal
        where created >= case
                             when _time_from is not null
                                 then _time_from
                             else now() - _last_interval::interval end;
end;
$$;
  

Ответ №5:

Хотя приведенный выше принятый ответ хорош, он немного устарел — для чтения требуется немного больше умственной энергии, чем необходимо, если вы работаете на Postgres 9.4 .

Старый способ (версии Postgres <9.4)

 startDate TIMESTAMP := endDate - $3 * INTERVAL '1 MONTH';
  

Новый способ (Postgres 9.4 )

 startDate TIMESTAMP := endDate - MAKE_INTERVAL(MONTHS => $3);
  

Если вы используете Postgres 9.4 , новая функция MAKE_INTERVAL() кажется гораздо более читаемой — вероятно, поэтому они ее создали.

Если вы хотите что-то, что вы можете запустить в своем редакторе, вот несколько примеров (я заменил исходную привязку переменной $3 числом 2 для примера за 2 месяца до текущей даты).

 SELECT CURRENT_DATE - 2 * INTERVAL '1 MONTH';

SELECT CURRENT_DATE - MAKE_INTERVAL(MONTHS => 2);