Перейти к содержанию

Типы данных PostgreSQL

Таблица задаёт не только названия полей, но и допустимое представление значений. Количество уроков, условная цена и момент публикации имеют разный смысл. Если хранить всё как текст, приложение сможет записать слово «много» вместо количества, однако потом не получит корректную числовую сортировку. Тип помогает базе понимать операции ещё до предметных ограничений.

Рассмотрим выбранные типы в PostgreSQL 17.11 на снимке catalog-db/lesson-04 из архива каталога. Исходный набор содержит шесть курсов. Программы и SQL не выполнялись; ниже объясняем ожидаемое представление данных. Файл восстановления отдельно создаёт общую учебную схему, поэтому все колонки доступны и в этой главе.

Число, строка и смысл значения

Числовой id курса имеет тип bigint. Его назначение — отличать записи, а не обозначать количество глав. Поле planned_lessons имеет тип integer, поскольку содержит положительное целое количество. Оба типа числовые, но нельзя заменять один другим в интерфейсе только на основании общей категории. Над идентификатором не имеет смысла вычислять среднее содержимое курса.

Название и публичный код записаны как text. Ограничение «название не пустое» будет задано отдельно: тип текста сам по себе допускает пустую строку. Также тип не объясняет, можно ли менять slug уже опубликованного курса. Это правило маршрутизации приложения, которое нужно отличать от технической возможности сохранить новую строку.

Посмотрим структуру без изменения данных:

SELECT column_name, data_type, is_nullable
FROM information_schema.columns
WHERE table_schema = 'catalog' AND table_name = 'courses'
ORDER BY ordinal_position;

Ожидаются, среди прочего, bigint для ключей, integer для планируемого количества, numeric для цены и timestamp with time zone для момента публикации. Представление метаданных использует полные названия типов. Сокращённая запись timestamptz в SQL означает тот же выбранный тип, а не отдельную разновидность времени.

В документации типов PostgreSQL можно найти более широкий набор. Нам не нужно вводить все типы сразу. Начальный договор должен соответствовать действиям каталога: сравнить количества, отсортировать момент публикации и показать текст. Выбор начинается с этих операций, а не с поиска самого короткого названия типа.

Точная условная цена

Поле price имеет тип numeric(10,2). В учебном наборе цены условные: ноль, 990, 1490, 1990 и 2490. Два десятичных знака позволяют представить дробную денежную величину. Мы не используем real для такого поля, поскольку приближённое представление числа может быть неудобно для точного сравнения и расчёта суммы.

Для полноты денежного договора общий reset исключает также специальное значение NaN: проверка требует price <> 'NaN'::numeric вместе с неотрицательностью. В правилах numeric PostgreSQL такие специальные значения рассматриваются отдельно; простого сравнения с нулём для их предметного исключения недостаточно. Наши исходные цены обычные конечные числа.

Сам тип не определяет валюту. Число 990 без валюты и назначения ещё не является полноценной коммерческой моделью. В серии подразумевается одна условная единица, чтобы разбирать SQL. Если продукт поддерживает несколько валют, хранить и суммировать значения вместе только потому, что они имеют одинаковый тип, было бы предметной ошибкой.

Рассмотрим вычисление над точными значениями:

SELECT slug, price, price * 0.90 AS discounted_price
FROM catalog.courses
WHERE slug IN ('js-browser', 'markdown-site')
ORDER BY id;

Ожидаемые суммы после умножения — 1791 и 891. Клиент может показать несколько десятичных знаков у выражения, поскольку тип результирующего вычисления не обязан иметь тот же масштаб, что сохранённая колонка. Оформление двух знаков на странице и правила округления при сохранении — два отдельных решения. Не используйте красивое форматирование как доказательство точности предметного расчёта.

Нулевая цена не означает отсутствующую. Для бесплатного курса записано настоящее число ноль. Для неизвестной цены в другой модели понадобилось бы отсутствующее значение и иное правило публикации. В нашей таблице цена обязательна, поэтому NULL запрещён. Это помогает отчётам различать бесплатный объект и объект с неопределённым состоянием, не придумывая значение автоматически.

Момент публикации

Момент события хранится как timestamptz. В исходных строках указан явный UTC-сдвиг. У draft-курса web-performance момент отсутствует, у опубликованного курса заполнен. Сам тип не знает об этом соответствии: связанное правило задано отдельным ограничением и будет объяснено в следующей главе.

Для вывода снимки устанавливают часовой пояс UTC. Посмотрите две строки с одним моментом публикации:

SELECT id, slug, published_at
FROM catalog.courses
WHERE published_at = TIMESTAMPTZ '2025-02-01 10:00+00'
ORDER BY id;

Ожидаются markdown-site и postgresql-web. Одинаковый момент законен: публикация двух курсов может произойти одновременно. Поэтому при сортировке ленты по времени позже добавим ключ как второй критерий. Тип времени позволяет сравнить события, но не делает каждое время уникальным идентификатором записи.

Название «с часовым поясом» не означает сохранение исходного названия зоны вроде Europe/Minsk внутри каждой строки. Выбранный тип представляет момент; сессия определяет его отображение. Если приложению нужно хранить локальное расписание и отдельную зону, модель должна содержать соответствующие поля. Такую задачу нельзя решить только переключением текущего отображения клиента.

Отсутствующее значение

Колонка description допускает NULL. У производительности и основ SEO описания пока нет. Это не пустая строка и не текст из четырёх букв NULL. Отсутствие участвует в SQL-сравнениях по особым правилам, поэтому проверять его через description = NULL неправильно. Для данной задачи используется IS NULL.

SELECT slug
FROM catalog.courses
WHERE description IS NULL
ORDER BY id;

Ожидаются два кода. Измените условие на IS NOT NULL, и получите остальные четыре курса. Такой результат связан с обязательностью значения, а не с длиной текста. Пустое описание '', если бы оно присутствовало, попало бы во вторую выборку. Проверка отсутствия и проверка содержательной строки не взаимозаменяемы.

Для карточки можно показать заменяющую подпись через COALESCE(description, 'Описание готовится'). Это представление результата: оно не заполняет колонку в базе. Редактор по-прежнему сможет отличить незаполненное поле. Если вместо этого сохранить подпись как настоящее описание, отсутствие станет трудно обнаружить в отчёте: служебный текст превратится в содержимое.

Похожее различие касается body урока. В исходных данных один учебный текст отсутствует, но название и позиция существуют. Такой набор показывает, почему тип текста и готовность материала должны обсуждаться отдельно. База хранит состояние, а приложение решает, можно ли выводить его читателю.

Преобразование и сохранённые значения

Выражение над колонкой может получить новый тип результата, не меняя сохранённое поле. Например, превращение количества глав в текст для подписи карточки не означает, что в таблице теперь хранится строка. Поэтому описание результата запроса и описание структуры нужно читать отдельно. Метаданные таблицы отвечают на второй вопрос, а состав SELECT — на первый.

Полезный мысленный опыт: сравните сортировку чисел два, десять и двадцать с сортировкой строк «2», «10» и «20». Числовой порядок соответствует количеству. Текстовый порядок следует правилам сравнения строк и может поставить «10» перед «2». Исправлять такой эффект оформлением карточки поздно: сначала требуется выбрать соответствующее представление данных или явное преобразование запроса.

Но приведение текста к числу тоже не проверяет все предметные условия. Строка «-1» может успешно стать целым, оставаясь недопустимым планом курса. Поэтому последовательность проверки состоит из разных вопросов: можно ли получить значение нужного типа, допустимо ли оно для каталога и какое действие приложение с ним выполняет. Следующая глава отвечает именно на второй вопрос.

Старые типы Transact-SQL относятся к SQL Server. Не переносите названия вроде DATETIME или специальные правила его IDENTITY без проверки диалекта. Здесь выбран конкретный набор PostgreSQL. Теперь значения получили подходящее представление; следующий шаг закрепит правила, которым должны соответствовать даже технически допустимые числа и строки.

Оглавление курса.