Learning Platform
Урок 04.02 · 18 мин
Начальный
TEXTVARCHARTIMESTAMPTIMESTAMPTZTIME ZONEINTERVALDATE

Текст: 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) не нужен почти никогда.

Три текстовых типа PostgreSQL

Под капотом — один и тот же varlena. Снаружи — три набора правил проверки.

CHAR(n)blank-padded
хранениеvarlena + пробелы
когдапочти никогда
VARCHAR(n)checked length
хранениеvarlena
когдаесть бизнес-правило на длину
TEXTбез проверок
хранениеvarlena
когдапо умолчанию

Посмотри на одинаковый размер хранения для всех трёх:

PostgreSQL

Видишь: 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). Часовой пояс используется в двух местах:

  1. При вставке — если в строке есть +03, PostgreSQL сдвигает значение в UTC и сохраняет.
  2. При выводе — PostgreSQL берёт UTC-значение и форматирует его в текущий часовой пояс сессии (SET TIME ZONE).

TIMESTAMP (без TZ) — это голая комбинация года, месяца, дня, часов, минут, секунд. Никаких преобразований. Что вставил — то и достал.

TIMESTAMP vs TIMESTAMPTZ

Слева — буквальная запись. Справа — точка во времени, представленная по-разному в разных зонах.

TIMESTAMPбез TZ
хранитбуквально (Y, M, D, h, m, s)
вставкачто записал, то и лежит
выводтак же без преобразования
когда«дата рождения», «дедлайн отчёта»
TIMESTAMPTZмомент в UTC
хранитUTC-микросекунды
вставкасдвигается из исходной TZ в UTC
выводформатируется в TZ сессии
когдавсё, что связано с событиями

Правило практики: для любого «когда произошло событие» — TIMESTAMPTZ. TIMESTAMP (без TZ) бери для абстрактных «гражданских» моментов: «расписание поезда 09:00 по Москве», «политика отчётности — конец первого квартала». То есть когда важна именно стенка часов в конкретной локали, а не глобальная точка во времени.

Один и тот же момент в разных TZ — посмотри на сессию:

PostgreSQL

Конструкция AT TIME ZONE 'имя' — это переключатель. Применённая к TIMESTAMPTZ, она снимает с него TZ и даёт «гражданское» время в указанной зоне (тип результата — TIMESTAMP). Применённая к TIMESTAMP, наоборот — «приклеивает» зону и превращает в TIMESTAMPTZ.

INTERVAL и арифметика дат

INTERVAL — это «продолжительность». Можно складывать и вычитать с DATE, TIMESTAMP, TIMESTAMPTZ.

Арифметика INTERVAL:

PostgreSQL

Особенность: 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 месяца от сегодня:

PostgreSQL

Заметь синтаксис placed_at::date — мы приводим TIMESTAMPTZ к DATE, чтобы убрать время и считать только дни.

DATE arithmetic: распространённые задачи

Сколько заказов в каждом месяце 2025 года:

PostgreSQL

DATE_TRUNC сбрасывает все компоненты ниже указанного уровня. DATE_TRUNC('month', x) — это «первый день месяца с x в нём, 00:00». Используется для группировки по неделям/месяцам/годам везде, где нужны временные ряды.

Другая полезная функция — EXTRACT:

Достаём части даты:

PostgreSQL

DOW (day of week) считает воскресенье = 0, суббота = 6. Если нужен ISO-понедельничный вариант (Mon=1, Sun=7) — используй ISODOW.

Проверка знанийKnowledge check
Сервер в Берлине (CET), пользователь в Москве (MSK). Создаёшь колонку created_at. Использовать TIMESTAMP или TIMESTAMPTZ — и почему это важно?
ОтветAnswer
TIMESTAMPTZ. С ним PostgreSQL сохраняет UTC-момент независимо от того, в какой TZ был сервер или клиент. При выводе каждый клиент увидит свой локальный час: московский — MSK, берлинский — CET, лондонский — UTC. С TIMESTAMP (без TZ) база сохранит буквальное «14:00» — и если клиент в Москве считает, что это MSK, а сервер в Берлине считает, что это CET, у тебя получится двухчасовой расхождение в отчётах. Хуже того: при переходе на летнее время в одних TZ и не в других — поведение становится непредсказуемым. Правило: 99% колонок «когда событие случилось» должны быть TIMESTAMPTZ.
datetime и часовые пояса в Python: дисциплина UTC Дата и время в DuckDB: DATE, TIME, TIMESTAMP, INTERVAL Constraints как код модели: CHECK, UNIQUE, NOT NULL

Чек-лист

  • В 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 — для отдельных компонент.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 4. Какое утверждение про TEXT и VARCHAR(n) в PostgreSQL верно?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 6