Текст: TEXT vs VARCHAR(n) vs CHAR(n)
В стандарте SQL текстовых типов три:
CHAR(n)— строка фиксированной длины. Если вставить 3 символа вCHAR(10), она будет дополнена пробелами до 10. Это.наследие COBOL и перфокартVARCHAR(n)— строка переменной длины, но не большеnсимволов. Лишнее — ошибка.TEXT— строка любой длины. До 1 ГБ.
В PostgreSQL они хранятся идентично — все три используют один внутренний тип varlena с переменной длиной. Разница только в проверках:
VARCHAR(n)проверяет, что длина ≤ n.CHAR(n)дополняет пробелами до n и проверяет.TEXTничего не проверяет.
Никакого выигрыша по скорости или диску у VARCHAR перед TEXT нет. Это типичный
Практика в PostgreSQL: используй TEXT по умолчанию. VARCHAR(n) бери, только если ограничение длины — часть бизнес-правила (например, телефонный номер строго 11 цифр). CHAR(n) не нужен почти никогда.
Под капотом — один и тот же varlena. Снаружи — три набора правил проверки.
Посмотри на одинаковый размер хранения для всех трёх:
Видишь: CHAR(20) тратит больше — потому что добивает пробелами. У TEXT и VARCHAR(50) — одинаковый расход.
Темпоральные типы: пять штук
В PostgreSQL пять основных типов для времени:
DATE— только дата. 4 байта.TIME— только время суток без даты. 8 байт.TIMESTAMP— дата + время без часового пояса. 8 байт.TIMESTAMPTZ(он жеTIMESTAMP WITH TIME ZONE) — дата + время с часовым поясом. 8 байт.INTERVAL— длительность (например, «3 дня 5 часов»). 16 байт.
Самый коварный — TIMESTAMPTZ. Название обманчиво.
Где живёт часовой пояс — самая частая ошибка
TIMESTAMPTZ не хранит часовой пояс. Внутри это просто момент в UTC (microseconds since 2000-01-01 UTC). Часовой пояс используется в двух местах:
- При вставке — если в строке есть
+03, PostgreSQL сдвигает значение в UTC и сохраняет. - При выводе — PostgreSQL берёт UTC-значение и форматирует его в текущий часовой пояс сессии (
SET TIME ZONE).
TIMESTAMP (без TZ) — это голая комбинация года, месяца, дня, часов, минут, секунд. Никаких преобразований. Что вставил — то и достал.
Слева — буквальная запись. Справа — точка во времени, представленная по-разному в разных зонах.
Правило практики: для любого «когда произошло событие» — TIMESTAMPTZ. TIMESTAMP (без TZ) бери для абстрактных «гражданских» моментов: «расписание поезда 09:00 по Москве», «политика отчётности — конец первого квартала». То есть когда важна именно стенка часов в конкретной локали, а не глобальная точка во времени.
Один и тот же момент в разных TZ — посмотри на сессию:
Конструкция AT TIME ZONE 'имя' — это переключатель. Применённая к TIMESTAMPTZ, она снимает с него TZ и даёт «гражданское» время в указанной зоне (тип результата — TIMESTAMP). Применённая к TIMESTAMP, наоборот — «приклеивает» зону и превращает в TIMESTAMPTZ.
INTERVAL и арифметика дат
INTERVAL — это «продолжительность». Можно складывать и вычитать с DATE, TIMESTAMP, TIMESTAMPTZ.
Арифметика INTERVAL:
Особенность: DATE - DATE даёт INTEGER (число дней), а TIMESTAMP - TIMESTAMP даёт INTERVAL. Это бывает удобно — но если ты ожидал INTERVAL от двух дат, придётся явно приводить: (d2 - d1) * INTERVAL '1 day'.
INTERVAL поддерживает
'1 month', '1 year' — но они не имеют фиксированной длительности в секундах. Февраль ≠ март.
now(), CURRENT_DATE, CURRENT_TIMESTAMP
PostgreSQL даёт несколько способов узнать «сейчас»:
CURRENT_DATE— сегодняшняя дата в сессии.CURRENT_TIME— текущее время с TZ.CURRENT_TIMESTAMP— текущий момент с TZ. Синонимnow().LOCALTIMESTAMP— текущий момент без TZ (зачищается зона).clock_timestamp()— действительно «сейчас», обновляется на каждом вызове.
Ключевая ловушка: now()/CURRENT_TIMESTAMP возвращают момент начала транзакции, а не «прямо сейчас». Если в одной транзакции вызвать now() десять раз — получишь десять одинаковых значений. clock_timestamp() каждый раз идёт за реальным временем.
Поработай со временем на реальных данных — заказы за последние 3 месяца от сегодня:
Заметь синтаксис placed_at::date — мы приводим TIMESTAMPTZ к DATE, чтобы убрать время и считать только дни.
DATE arithmetic: распространённые задачи
Сколько заказов в каждом месяце 2025 года:
DATE_TRUNC сбрасывает все компоненты ниже указанного уровня. DATE_TRUNC('month', x) — это «первый день месяца с x в нём, 00:00». Используется для группировки по неделям/месяцам/годам везде, где нужны временные ряды.
Другая полезная функция — EXTRACT:
Достаём части даты:
DOW (day of week) считает воскресенье = 0, суббота = 6. Если нужен ISO-понедельничный вариант (Mon=1, Sun=7) — используй ISODOW.
Чек-лист
- В PostgreSQL
TEXT,VARCHAR(n),CHAR(n)хранятся одинаково. БериTEXTпо умолчанию. CHAR(n)дополняет пробелами — почти всегда не то, что нужно.TIMESTAMPхранит буквальное «когда»,TIMESTAMPTZ— UTC-момент. Для событий — всегдаTIMESTAMPTZ.AT TIME ZONEпереключает представление; не «прикрепляет TZ к хранению».INTERVAL— длительность.DATE - DATEдаёт INT (дни),TIMESTAMP - TIMESTAMP— INTERVAL.now()/CURRENT_TIMESTAMP— момент начала транзакции,clock_timestamp()— действительно сейчас.DATE_TRUNCдля группировки по неделям/месяцам/годам,EXTRACT— для отдельных компонент.