Здравствуйте, эксперты PostgreSQL.
Я пытаюсь понять, почему эти два логических выражения возвращают разные результаты.
Первый возвращает TRUE, тогда как второй возвращает FALSE.
SELECT CAST('2019-01-01T12:00:00' AS TIMESTAMP) - CAST('2018-01-01T13:00:00' AS TIMESTAMP) <= INTERVAL '365 DAYS',
CAST('2019-01-01T12:00:00' AS TIMESTAMP) - CAST('2018-01-01T13:00:00' AS TIMESTAMP) <= INTERVAL '1 YEAR';
Ни 2019, ни 2018 годы не были високосными.
Я ожидал, что для невисокосных лет интервал в 1 год будет эквивалентен интервалу в 365 дней, но я явно ошибался.
Протестировано с PostgreSQL 15.
Ваша помощь будет высоко оценена!
Редактировать: Так что похоже, что это скорее баг, чем фича. «Основы SQL IEC/ISO 9075-2:2016» определяют 2 типа интервалов. Один называется интервалом год-месяц, а другой - дневным интервалом. Каждый тип сравним только сам с собой. Следовательно, второй предикат должен был вызвать ошибку для несовместимых типов. Это избавило бы от многих головных болей всех, кто его использует. Если есть участники PostgreSQL, читающие это, я думаю, что это следует рассмотреть для реализации в будущем выпуске.
Похоже, что Postgres определяет интервал в 1 год как 365.25
дней.
Это может быть связано с тем, что тип интервала не включает базовое время начала и окончания. Это размер без позиции.
Если хотите, можете сравнить 1 metre
и 1 metre from my chair
.
Так что интервал не знает, о каком году вы говорите.
Таким образом, в среднем это составляет 365.25
дней, что примерно равно тому, что вы получили бы, если бы усредняли большинство 4-летних периодов.
Вы можете проверить это с помощью:
select extract(epoch from interval '1 year')
Это дает 31557600
, и вы можете сделать математику оттуда.
Обновлено: после некоторых комментариев мне стало любопытно, и я обнаружил:
а) это сложнее, чем я мог себе представить
б) это немного движущаяся цель.
Этот коммит в апреле 2022 года претендует на отмену регрессии, которая (неправильно) из-за округления составила 365 дней, а не 365,25. Он ссылается на фиксацию, которая (очевидно) представила это, что было в апреле 2021 года. В исправлении конкретно упоминается, что оно зависит от DAYS_PER_YEAR, кратного 0,25.
https://github.com/postgres/postgres/commit/f2a2bf66c87e14f07aefe23cbbe2f2d9edcd9734
Версия Postgres, в которой я получил 365.25, — 14.6.
SELECT VERSION()
дает
PostgreSQL 14.6 (Debian 14.6-1.pgdg110+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 10.2.1-6) 10.2.1 20210110, 64-bit
Так что я предполагаю, что моя установка была установлена до того, как была введена проблема с округлением, а у @Atmo есть одна после этого и до того, как это исправление было выпущено.
(Все выводы, сделанные здесь, были сделаны на основе чтения комментариев, а не кода).
Даты и время. Жесткий.
Это нарушает стандарт SQL 2016...
Но это не объясняет его наблюдения, так как 365,25 даже длиннее, чем 365, так что его второе неравенство, безусловно, было бы верным. 31557600 секунд в 1 году - это особенность "эпохи", а не интервала вообще. Особенность, которая объясняет его наблюдение, состоит в том, что месяцы состоят из 30 дней, что означает 360 дней в году.
Согласованный. Часть размера без позиции кажется действительной, но, по крайней мере, в моей БД год без позиционирования рядом с датой кажется равным 360 дням, и select extract(epoch from interval '1 year')
возвращает 31536000
, а не 31557600
(365,00 дней).
Ваше редактирование объясняет, почему SomeDate + interval '1 year'
дал нам другой результат. Теперь, как прокомментировал @AdrianKlaver, interval '1 year'
= interval 360 days
, скорее всего потому, что 1 год — это ровно 12 месяцев и для простоты подсчета 1 месяц (без уточнения, о каком месяце идет речь) округлили до 30 дней. Это должно иметь смысл для всех. В результате легче рассчитать (в контекстах, которые в основном исчезли со старым оборудованием) 360 дней в году. Я не знаю, что думать о том, что его модифицировал extract (epoch from ...)
.
Однако, что важно, при добавлении интервалов к датам SomeDate + 'interval 1 year
= SomeDate + interval '365 days'
без учета високосных лет.
select '360 days'::interval = '1 year'::interval; t
из Datetime ... при условии, что 30 дней в месяц ....select '12 months'::interval = '1 year'::interval; t
.