У нас есть таблица, заполненная данными из устаревшего отчета другой системы. Столбцы этой таблицы отражают ту же структуру отчета.
Вот сокращенная структура таблицы:
CREATE TABLE IF NOT EXISTS LEGACY_TABLE (
REPORT_DATE DATE NOT NULL,
EVENT_ID BIGINT PRIMARY KEY NOT NULL,
START_HOUR TIMESTAMP WITHOUT TIME ZONE,
END_HOUR TIME WITHOUT TIME ZONE,
EXPECTED_HOUR TIME WITHOUT TIME ZONE
);
Мы реорганизуем эту таблицу, чтобы иметь дело с разными часовыми поясами разных клиентов. Новая структура будет примерно такой:
CREATE TABLE IF NOT EXISTS LEGACY_TABLE (
REPORT_DATE DATE NOT NULL,
EVENT_ID BIGINT PRIMARY KEY NOT NULL,
START_HOUR TIMESTAMP WITH TIME ZONE,
END_HOUR TIME WITH TIME ZONE,
EXPECTED_HOUR TIME WITH TIME ZONE
);
Эти поля часов представляют собой определенный момент времени в течение дня, представленный столбцом REPORT_DATE. Я имею в виду, что каждый столбец TIME представляет момент в течение дня, указанный в REPORT_DATE.
Некоторые другие моменты, которые следует учитывать:
Но теперь к проблеме. Значения этих столбцов используются для многократного вычисления других значений в нашей системе, примерно так:
START_HOUR - END_HOUR (the result of this operation is currently being casted to TIME WITHOUT TIME ZONE)
START_HOUR < END_HOUR
START_HOUR + EXPECTED_HOUR
EXPECTED_HOUR - END_HOUR
EXPECTED_HOUR < '05:00'
После некоторого исследования я обнаружил, что не рекомендуется использовать тип TIME WITH TIME ZONE (Время Postgres с равенством часовых поясов), и теперь я немного запутался в том, как лучше всего реорганизовать эту таблицу для работы с разными часовыми поясами и обработки различных операций с столбцами, которые нам нужны. к.
Кроме того, я уже знаю, что можно безопасно вычесть два столбца типа TIMESTAMP WITH TIME ZONE. Эта операция вычитания учитывает изменения DST (Вычитание двух столбцов типа timestamp с часовым поясом), но как насчет других? И тот, который вычитает ВРЕМЯ из TIMESTAMP ?.
А что касается рефакторинга таблиц, стоит ли нам в любом случае использовать TIME WITH TIME ZONE? Стоит ли нам продолжать использовать TIME WITHOUT TIME ZONE? Или лучше вообще забыть тип TIME и объединить ДАТУ с ВРЕМЯ и изменить столбцы на TIMESTAMP WITH TIME ZONE?
Я думаю, что эти вопросы связаны между собой, потому что новые типы столбцов, которые мы выбираем для использования, будут определять, как мы будем работать с ними.
IIUC, в настоящее время вы игнорируете часть даты START_HOUR? Или у вас есть ограничение CHECK, обеспечивающее то же самое, что и REPORT_DATE?
@ErwinBrandstetter: В настоящее время мы сохраняем часть даты, но для всех текущих вычислений мы используем только часть времени. Когда мы обрабатываем устаревший отчет, мы гарантируем, что часть даты и REPORT_DATE совпадают.
we need to combine the timezone of the client (we have this info). Как именно получить эту информацию без сохранения?
Мы сохраняем это. В таблице клиентов. Мы знаем, какой клиент импортирует отчет, а также знаем часовой пояс.





Вы утверждали, что:
every TIME column represents a moment during the day specified in
REPORT_DATE.
Таким образом, вы никогда пересекаете линию дат в той же строке. Я предлагаю сохранить 1x date, 3x time и часовой пояс (как столбец text или FK):
CREATE TABLE legacy_table (
event_id bigint PRIMARY KEY NOT NULL
, report_date date NOT NULL
, start_hour time
, end_hour time
, expected_hour time
, tz text -- time zone
);
Как вы уже нашли, timetz (time with time zone) обычно следует избегать. Он не может правильно работать с правилами DST (daylight saving ттrong>ime).
Итак, в основном то, что у вас уже было. Просто отбросьте компонент даты из start_hour, это мертвый груз. Отобразите timestamp на time, чтобы обрезать дату. Нравится: (timestamp '2018-03-25 1:00:00')::time
tz может быть любой строкой, принятой конструкцией AT TIME ZONE, но для надежной работы с разными часовыми поясами лучше использовать исключительно имена часовых поясов. Любой name, который вы найдете в системный каталог pg_timezone_names.
Чтобы оптимизировать хранилище, вы можете собрать имена разрешенных часовых поясов в небольшой поисковой таблице и заменить tz text на tz_id int REFERENCES my_tz_table.
Два примера строк с DST и без него:
INSERT INTO legacy_table VALUES
(1, '2018-03-25', '1:00', '3:00', '2:00', 'Europe/Vienna') -- sadly, with DST
, (2, '2018-03-25', '1:00', '3:00', '2:00', 'Europe/Moscow'); -- Russians got rid of DST
В целях представления или расчетов вы можете делать такие вещи, как:
SELECT (report_date + start_hour) AT TIME ZONE tz AT TIME ZONE 'UTC' AS start_utc
, (report_date + end_hour) AT TIME ZONE tz AT TIME ZONE 'UTC' AS end_utc
, (report_date + expected_hour) AT TIME ZONE tz AT TIME ZONE 'UTC' AS expected_utc
-- START_HOUR - END_HOUR
, (report_date + start_hour) AT TIME ZONE tz
- (report_date + end_hour) AT TIME ZONE tz AS start_minus_end
FROM legacy_table;
Вы можете создать один или несколько взгляды для быстрого отображения строк по мере необходимости. Таблица предназначена для хранения информации, которую вы нужно.
Обратите внимание на круглые скобки! В противном случае оператор + будет связываться до AT TIME ZONE из-за приоритет оператора.
И вот результаты:
db <> рабочий пример здесь
Поскольку в Вене манипулируют временем (как и в любом другом месте, где действуют глупые правила летнего времени), вы получаете "удивительные" результаты.
Связанный:
Спасибо за исчерпывающий ответ! Однако у меня есть некоторые вопросы: 1) Почему добавление report_date + end_hour и report_date + expected_hour в tz 'Europe/Vienna' дает тот же результат? Учитывая, что начало и ожидание отличаются, как в вашем db <> рабочий пример. 2) Зачем снова применять AT TIME ZONE, используя UTC? Когда мы впервые применяем tz с помощью клиента tz, у нас уже нет метки времени в часовом поясе клиента? Это немного сбивает меня с толку, поскольку при выполнении START_HOUR - END_HOUR вы не применяете AT TIME ZONE дважды.
@Luiz: 1) Потому что время на 2018-03-25 02:00 в ЕС продвинулось на час вперед из-за глупых правил перехода на летнее время. Таким образом, 3 часа ночи - это то же самое, что 2 часа ночи в этот день. 2) Следуйте моя добавленная ссылка для подробного объяснения. Второй AT TIME ZONE нам не нужен для расчетов, поэтому я его там не использую.
Не думаю, что я первый, кто проголосует за ответ! Спасибо, Эрвин, за драгоценное время и усилия
@ErwinBrandstetter Просто чтобы проверить: если мне нужно вычесть TIME (поскольку я сейчас использую TIME WITHOUT TIME ZONE, TIME уже сохранен в клиентском tz) из TIMESTAMPTZ, правильный подход - сначала применить AT TIME ZONE (используя тот же TZ, в каком TIME был сохранен) на TIMESTAMPTZ, верно (предполагается, что у меня нет ДАТЫ для привязки к части ВРЕМЕНИ)? Судя по моим тестам, это кажется правильным подходом, но я не совсем уверен, что он охватит все случаи ...
Это зависит от того, что именно должно означать "вычесть TIME". Если предполагается, что время учитывает часовой пояс, в котором оно находится, вы должны вычесть из timestamptz, а не из timestamp. См. Это базовое сравнение: dbfiddle.uk/….
Я лично предпочитаю использовать целочисленный столбец, содержащий временную метку unix, хотя в 2038 году вам нужно будет изменить этот тип столбца.