Динамическое построение интервала

Мы уже умеем использовать в запросах интервалы. Например так:

SELECT p.purchase_date,
       p.purchase_date + interval '1 month' AS add_month
  FROM purchase p
# purchase_date add_month
1 2019-03-11 17:15:02.206+07 2019-04-11 17:15:02.206+07
2 2019-03-10 17:12:34.206+07 2019-04-10 17:12:34.206+07
3 2019-04-05 16:12:33.206+07 2019-05-05 16:12:33.206+07
4 2019-07-06 20:12:33.206+07 2019-08-06 20:12:33.206+07
... ... ...

Бывает так, что на момент написания запроса неизвестно, какое количество месяцев, дней, минут нужно использовать. Эти значения могут храниться в базе данных или вычисляться по формуле.

Для примера мы будем использовать константу из подзапроса, как будто мы ее получили из таблицы БД:

SELECT c.count_months,
       p.purchase_date,
       p.purchase_date + interval '1 month' AS add_month
  FROM purchase p,
       (SELECT 5 AS count_months) c
# count_months purchase_date add_month
1 5 2019-03-11 17:15:02.206+07 2019-04-11 17:15:02.206+07
2 5 2019-03-10 17:12:34.206+07 2019-04-10 17:12:34.206+07
3 5 2019-04-05 16:12:33.206+07 2019-05-05 16:12:33.206+07
4 5 2019-07-06 20:12:33.206+07 2019-08-06 20:12:33.206+07
... ... ... ...

И вроде бы все просто: нужно заменить interval '1 month' на interval c.count_months || ' month' и должно сработать. Но нет:

SELECT c.count_months,
       p.purchase_date,
       p.purchase_date + interval c.count_months || ' month' AS add_month
  FROM purchase p,
       (SELECT 5 AS count_months) c
error: syntax error at or near "."

Избежать этой ошибки можно двумя способами: воспользовавшись функцией make_interval или приведением строки к типу interval.

make_interval

В PostgreSQL есть функция создания интервала make_interval со следующей сигнатурой:

make_interval(
   years int DEFAULT 0, 
   months int DEFAULT 0, 
   weeks int DEFAULT 0, 
   days int DEFAULT 0, 
   hours int DEFAULT 0, 
   mins int DEFAULT 0, 
   secs double precision DEFAULT 0.0
)

Использовать ее можно последовательно перечисляя параметры:

SELECT make_interval(1, 6, 0, 10)
# make_interval
1 1 year 6 mons 10 days

Такой способ не удобен, если нам нужно передать только пару параметров. В этом случае можно воспользоваться стрелочной нотацией:

SELECT make_interval(months => 6)
# make_interval
1 6 mons

Воспользуемся make_interval для добавления динамического количества месяцев к дате продажи:

SELECT c.count_months,
       p.purchase_date,
       p.purchase_date + make_interval(months => c.count_months) AS add_month
  FROM purchase p,
       (SELECT 5 AS count_months) c
# count_months purchase_date add_month
1 5 2019-03-11 17:15:02.206+07 2019-08-11 17:15:02.206+07
2 5 2019-03-10 17:12:34.206+07 2019-08-10 17:12:34.206+07
3 5 2019-04-05 16:12:33.206+07 2019-09-05 16:12:33.206+07
4 5 2019-07-06 20:12:33.206+07 2019-12-06 20:12:33.206+07
... ... ... ...

Преобразование строки в интервал

Преобразовать строку в интервал можно с помощью ::

SELECT '5 months'::interval
# interval
1 5 mons

Решим нашу основную задачу

SELECT c.count_months,
       p.purchase_date,
       p.purchase_date + (c.count_months || ' months')::interval AS add_month
  FROM purchase p,
       (SELECT 5 AS count_months) c
# count_months purchase_date add_month
1 5 2019-03-11 17:15:02.206+07 2019-08-11 17:15:02.206+07
2 5 2019-03-10 17:12:34.206+07 2019-08-10 17:12:34.206+07
3 5 2019-04-05 16:12:33.206+07 2019-09-05 16:12:33.206+07
4 5 2019-07-06 20:12:33.206+07 2019-12-06 20:12:33.206+07
... ... ... ...

9.10 Временные интервалы

9.12 Начало и конец месяца