Типы данных в PostgreSQL: пошаговая инструкция
За 30–40 минут вы создадите таблицу в PostgreSQL 16.x, разберётесь с числами, строками, датами, JSONB и массивами и научитесь проверять, что данные хранятся без потери точности.

За 30–40 минут вы создадите учебную таблицу в PostgreSQL 16.x и проверите на реальных запросах, чем отличаются integer, numeric, text, timestamptz, jsonb и массивы. Это важно до первой миграции: неверный тип может округлить деньги, усложнить поиск по датам или превратить простую проверку в набор исключений.
Что подготовить до начала
Нужен работающий PostgreSQL 16.x и доступ к любой базе, в которой вы можете создавать и удалять учебные таблицы. Подойдёт psql, DBeaver, DataGrip или другой SQL-клиент. Команды ниже используют только встроенные типы и не требуют расширений.
- подключение к базе данных;
- права на создание таблицы и выполнение запросов;
- клиент, который показывает результаты SQL-запросов;
- свободное имя таблицы
data_type_demo— пример удаляет её перед созданием.
Сноска. Тип данных — правило, которое определяет, какие значения столбец принимает и как PostgreSQL хранит, сравнивает и обрабатывает их. Ограничение CHECK проверяет условие для каждой строки и не даёт записать заведомо неверное значение.
Тип выбирают не по внешнему виду значения, а по операциям, которые приложение будет выполнять с ним.
Шаг 1. Создайте таблицу с базовыми типами
Сначала соберите небольшой каталог товаров. Здесь намеренно используются разные типы: идентификатор, текст, цена, количество, признак доступности, дата и время, JSON-документ и массив тегов.
DROP TABLE IF EXISTS data_type_demo;
CREATE TABLE data_type_demo (
product_id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name text NOT NULL,
price numeric(12, 2) NOT NULL CHECK (price >= 0),
stock integer NOT NULL CHECK (stock >= 0),
is_available boolean NOT NULL DEFAULT true,
published_on date,
updated_at timestamptz NOT NULL DEFAULT now(),
attributes jsonb NOT NULL DEFAULT '{}'::jsonb,
tags text[] NOT NULL DEFAULT '{}'
);
INSERT INTO data_type_demo (
name,
price,
stock,
is_available,
published_on,
updated_at,
attributes,
tags
) VALUES (
'Механическая клавиатура',
7490.00,
18,
true,
DATE '2025-01-15',
TIMESTAMPTZ '2025-01-15 10:30:00+03',
'{"switches": "tactile", "layout": "ru-en", "wireless": true}'::jsonb,
ARRAY['клавиатура', 'механика', 'беспроводная']
);
SELECT * FROM data_type_demo;
Почему так: numeric(12, 2) хранит точное десятичное число с двумя знаками после запятой, а timestamptz сохраняет момент времени с учётом часового пояса.
Проверка: запрос вернул одну строку, у неё появился product_id, цена равна 7490.00, а в tags отображается массив из трёх элементов.
Шаг 2. Разберите числа и строки
Для счётчиков и небольших целых значений обычно достаточно integer. Для денежных сумм, процентов и измерений, где нельзя допускать двоичного округления, используйте numeric. Типы real и double precision подходят для научных и приблизительных расчётов, но не являются заменой точному денежному типу.
SELECT
10::integer / 4::integer AS integer_result,
10::numeric / 4::numeric AS numeric_result,
7490.00::numeric(12, 2) AS exact_price,
name::text AS product_name,
length(name) AS name_length
FROM data_type_demo;
Целочисленное деление возвращает целую часть, а деление numeric сохраняет дробный результат. Явное приведение через :: делает намерение запроса понятным и снижает риск неожиданного преобразования.
Проверка: в первой колонке вы увидите 2, во второй — 2.500000... или эквивалентное значение, а длина названия будет положительным целым числом.
Шаг 3. Выберите правильный тип для дат и времени
date хранит календарную дату без времени: например, день публикации или дату рождения. timestamp without time zone хранит дату и время без часового пояса. Для событий, которые происходят в конкретный момент, обычно безопаснее timestamptz; PostgreSQL хранит такой момент, а показывает его с учётом часового пояса текущей сессии.
SHOW timezone;
SELECT
published_on,
updated_at,
updated_at AT TIME ZONE 'UTC' AS updated_at_utc,
updated_at >= now() - interval '30 days' AS changed_recently
FROM data_type_demo;
Не записывайте даты в формате вроде 01/02/2025, если значение приходит от пользователя: смысл может зависеть от региональных настроек. ISO-формат 2025-02-01 однозначнее.
Проверка: первый запрос покажет часовой пояс сессии, а второй — дату, момент обновления и логическое значение в колонке changed_recently.
Шаг 4. Добавьте JSONB и массивы
jsonb подходит для небольшого набора полей, который меняется от записи к записи: характеристик товара, настроек или необязательных атрибутов. Для постоянных полей лучше отдельные столбцы: они проще индексируются, проверяются ограничениями и читаются запросами.
SELECT
name,
attributes ->> 'layout' AS layout,
(attributes ->> 'wireless')::boolean AS wireless,
tags[1] AS first_tag
FROM data_type_demo
WHERE attributes @> '{"wireless": true}'::jsonb
AND 'механика' = ANY(tags);
Оператор ->> извлекает значение JSON как текст, @> проверяет наличие фрагмента JSON, а ANY ищет значение внутри массива.
Проверка: запрос вернёт товар, его раскладку ru-en, значение true и первый тег.
Шаг 5. Проверьте ограничения и NULL
NOT NULL запрещает отсутствие обязательного значения, DEFAULT подставляет значение, если его не передали, а CHECK блокирует нарушение условия. NULL — не ноль и не пустая строка, а отсутствие известного значения; сравнение с ним через = не даёт обычного true.
SELECT
count(*) AS total_products,
count(published_on) AS products_with_publish_date,
count(*) FILTER (WHERE stock > 0) AS products_in_stock
FROM data_type_demo;
-- Этот запрос должен завершиться ошибкой из-за CHECK (price >= 0).
INSERT INTO data_type_demo (name, price, stock)
VALUES ('Тестовый товар', -1.00, 1);
Сначала проверьте данные в запросе, затем добавляйте ограничения в рабочую схему: существующие строки должны им соответствовать. В учебной таблице отрицательная цена намеренно вызовет ошибку.
Проверка: агрегатный запрос вернёт три счётчика, а вставка завершится сообщением о нарушении ограничения data_type_demo_price_check или его автоматически созданного имени.
Если не получилось
- «permission denied for schema public». У пользователя нет права создавать таблицы в схеме. Подключитесь под ролью с нужными правами или попросите администратора выдать доступ; не переносите пароль администратора в приложение.
- «relation data_type_demo already exists». Если вы убрали
DROP TABLE, удалите старую учебную таблицу осознанно либо выберите другое имя. Не удаляйте таблицу с рабочими данными. - «invalid input syntax for type numeric». В запрос попала строка с неподходящим разделителем или символом валюты. Перед записью преобразуйте ввод приложения в число, а не отправляйте строку вроде
7 490 ₽. - «operator does not exist: jsonb @> text». Правая часть не приведена к JSONB. Используйте явное приведение, как в примере:
'{"wireless": true}'::jsonb. - Дата отображается на несколько часов иначе. Проверьте
SHOW timezoneи различие междуtimestampиtimestamptz. Для событий фиксируйте часовой пояс при записи, а не исправляйте дату в интерфейсе.
Что выбрать в новом проекте
Начните с типов, которые выражают бизнес-смысл, а не с самого широкого контейнера. Столбец price должен быть числом, is_available — логическим значением, а характеристики, состав которых действительно меняется, можно оставить в jsonb.
- идентификаторы и счётчики —
integerилиbigint, если диапазонаintegerнедостаточно; - день без времени —
date; - момент события —
timestamptz; - деньги и точные измерения —
numericс явно заданной точностью; - обязательный короткий или длинный текст — обычно
textс ограничением длины на уровне приложения или черезCHECK; - переменный набор атрибутов —
jsonb, но только если отдельные столбцы не дают более ясной схемы.
Что делать дальше
Сохраните учебный запрос и замените пример товара двумя-тремя реальными записями из проекта. Затем проверьте границы: максимальную цену, нулевой остаток, пустой JSON, отсутствующую дату и длинную строку. После этого изучите индексы для ваших фильтров и измерьте планы запросов через EXPLAIN: тип данных сам по себе не ускоряет поиск, если запрос не соответствует индексу.
Субтитры и таймкоды: Расшифровка речи и субтитры по правилам читаемости: 42 знака в строке, две строки, минимум секунда на реплику.
Спросить за 40 ₽Источники: PostgreSQL 16 Documentation: Data Types, PostgreSQL 16 Documentation: CREATE TABLE
Частые вопросы
Можно ли хранить деньги в PostgreSQL в типе double precision?
Чем text отличается от varchar?
Когда выбрать jsonb вместо отдельных столбцов?
Что будет, если я не успел выбрать тип до создания таблицы?
Почему timestamptz показывает другое время на разных компьютерах?
Нужно ли использовать массивы PostgreSQL для тегов?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.