В прошлом блоке мы разобрались, кто покупает в «Кубике», и Мира осталась с агентством. Теперь новый заказчик: Отто, операционный директор городской доставки «Гонец» в Екатеринбурге. В его диспетчерской на всю стену висит карта зон, и он привык, что цифры на ней не спорят с курьерами. Его база называется courier: зоны города (zones), курьеры (couriers), их смены (shifts), клиенты (customers) и 41 242 заказа (orders) с марта по июнь 2026 года.
Ким посчитал быстро и честно отправил то, что выдал запрос. Запрос не соврал: он сравнил два числа, как его попросили. Соврало сравнение, и разобраться в нём значит сохранить клиента.
Весь блок про время. И первый же вопрос Отто показывает, почему с ним надо обращаться осторожно.
Записка «в девять» и билет на самолёт
Представь записку на холодильнике: «Встреча в 9:00». В 9:00 где? Если ты живёшь в Екатеринбурге, понятно и так. Но перешли записку другу в Москву, и он придёт на два часа позже. Записка хранит показания часов на стене, а не момент на общей шкале.
Билет на самолёт устроен иначе: «вылет 9:00 по Екатеринбургу». Это конкретный момент, и в любом городе его можно пересчитать на местные часы.
В PostgreSQL для этого есть два типа:
- timestamp это записка. Дата и время без пояса:
2026-03-01 09:08:54. - timestamptz это билет. Конкретный момент. База хранит его в UTC, а показывает в поясе текущей сессии. В нашей песочнице сессия живёт в UTC, поэтому в конце значения видно
+00.
Третий тип, date, хранит только день: 2026-03-01.
Два приложения, два пояса
Посмотри на первый заказ:
SELECT id, created_at, picked_up_at, delivered_at
FROM orders
WHERE id = 1;
id | created_at | picked_up_at | delivered_at
----+---------------------+------------------------+------------------------
1 | 2026-03-01 09:08:54 | 2026-03-01 04:18:38+00 | 2026-03-01 04:34:21+00
Заказ создан в 09:08, забран в 04:18, доставлен в 04:34. Курьер приехал за пять часов до того, как клиент нажал кнопку?
Нет. Поля пишут разные системы. created_at и promised_at пишет сайт, и пишет местное время Екатеринбурга, как часы на стене: это timestamp. picked_up_at и delivered_at пишет приложение курьера, и пишет моменты в UTC: это timestamptz. Екатеринбург живёт в UTC+5, поэтому 04:34 по UTC это 09:34 по местному. Доставка заняла 25 минут, всё в порядке.
Откуда взялась машина времени
Ким написал очевидный запрос:
SELECT count(*)
FROM orders
WHERE delivered_at < created_at;
count
-------
38720
Чтобы сравнить timestamptz с timestamp, PostgreSQL молча превращает записку в билет. Какой город подставить? Пояс сессии, то есть UTC. Местные 09:08 становятся 09:08 по UTC, хотя это момент 04:08 по UTC. Каждый заказ «сдвигается» на пять часов вперёд, и почти любая доставка оказывается раньше него.
Ошибки в данных нет. Ошибка в сравнении: сравнили часы Екатеринбурга с часами Гринвича.
урок из курса
Урок из курса «Аналитика данных с нуля». Курс покупается один раз и остаётся навсегда: 500 ₽.
Оплата пока не подключена: премиум и курсы открываются промокодом.