SQLLab
Все статьи

Типы данных в PostgreSQL: как выбрать правильно

Типы данных в PostgreSQL: INTEGER vs BIGINT vs NUMERIC, VARCHAR vs TEXT, TIMESTAMP vs TIMESTAMPTZ, UUID, BOOLEAN, ENUM. Влияние выбора на производительность.

29 марта 2026 г.·5 мин чтения·

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

Числовые типы

ТипРазмерДиапазон
SMALLINT2 байта−32 768 … 32 767
INTEGER4 байта−2.1 млрд … 2.1 млрд
BIGINT8 байт−9.2 × 10¹⁸ … 9.2 × 10¹⁸
NUMERIC(p,s)переменныйпроизвольная точность
REAL4 байта~6 знаков точности
DOUBLE PRECISION8 байт~15 знаков точности

Когда что использовать:

  • INTEGER — счётчики, ID (если таблица не вырастет до 2 млрд строк), возраст, количество.
  • BIGINT — ID в высоконагруженных системах, временны́е метки в миллисекундах, финансовые идентификаторы.
  • NUMERIC — деньги, физические измерения, где нужна точность. Никогда не храните деньги в FLOAT/REAL.
  • DOUBLE PRECISION — научные расчёты, координаты (с пониманием погрешности).
-- Правильно: деньги в NUMERIC
CREATE TABLE invoices (
  invoice_id BIGSERIAL PRIMARY KEY,
  amount     NUMERIC(15, 2) NOT NULL, -- до 999 млрд с копейками
  tax_rate   NUMERIC(5, 4)            -- например, 0.2000
);

-- Неправильно: деньги в FLOAT (потеря точности при вычислениях)
-- amount FLOAT  -- никогда так не делайте

Почему FLOAT опасен для денег:

SELECT 0.1::FLOAT + 0.2::FLOAT;
-- Результат: 0.30000000000000004
SELECT 0.1::NUMERIC + 0.2::NUMERIC;
-- Результат: 0.3

Текстовые типы: просто используйте TEXT

В PostgreSQL есть три варианта:

  • CHAR(n) — строка фиксированной длины, дополняется пробелами. Почти никогда не нужен.
  • VARCHAR(n) — строка с максимальной длиной.
  • TEXT — строка без ограничений.

Производительность TEXT и VARCHAR в PostgreSQL одинакова — оба хранятся одинаково внутри. Ограничение VARCHAR(n) — это просто CHECK-констрейнт на длину.

-- Нет смысла писать VARCHAR(255) вместо TEXT
-- Используйте TEXT везде, добавляйте ограничение только если оно осмысленно
CREATE TABLE users (
  username   TEXT NOT NULL CHECK (LENGTH(username) BETWEEN 3 AND 50),
  email      TEXT NOT NULL,
  country_code CHAR(2)  -- вот тут CHAR(2) оправдан: строго 2 символа
);

Типы дат и времени

ТипОписание
DATEТолько дата
TIMEТолько время (без даты, без TZ)
TIMESTAMPДата + время, без временно́й зоны
TIMESTAMPTZДата + время, с временно́й зоной (хранит UTC)
INTERVALПромежуток времени

Главное правило: всегда используйте TIMESTAMPTZ.

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

TIMESTAMPTZ хранит UTC внутри и автоматически конвертирует в текущую временну́ю зону при отображении.

-- Правильно
CREATE TABLE events (
  event_id   BIGSERIAL PRIMARY KEY,
  title      TEXT NOT NULL,
  starts_at  TIMESTAMPTZ NOT NULL,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

-- Операции с TIMESTAMPTZ работают корректно при переводе часов
SELECT *
FROM events
WHERE starts_at BETWEEN NOW() AND NOW() + INTERVAL '7 days';

DATE оправдан, когда время действительно не нужно: дата рождения, дата окончания подписки, рабочий день.

BOOLEAN

Занимает 1 байт. Принимает TRUE/FALSE/NULL. Не нужно хранить 0/1 в INTEGER или строки 'yes'/'no'.

ALTER TABLE users ADD COLUMN is_active  BOOLEAN NOT NULL DEFAULT TRUE;
ALTER TABLE users ADD COLUMN is_premium BOOLEAN NOT NULL DEFAULT FALSE;

-- Индекс на булевом поле полезен, только если значения неравномерны
-- Например, индекс на is_active оправдан, если 95% пользователей активны
CREATE INDEX idx_users_inactive ON users(user_id) WHERE is_active = FALSE;

UUID

UUID — 128-битный идентификатор, гарантированно уникальный без координации между серверами.

-- Включить расширение для генерации UUID v4
CREATE EXTENSION IF NOT EXISTS pgcrypto;

CREATE TABLE sessions (
  session_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id    INT REFERENCES users(user_id),
  expires_at TIMESTAMPTZ NOT NULL
);

Подробнее о выборе между UUID и SERIAL — в статье о первичных ключах.

ENUM vs справочная таблица

PostgreSQL поддерживает пользовательские ENUM-типы:

CREATE TYPE order_status AS ENUM ('pending', 'paid', 'shipped', 'delivered', 'cancelled');

CREATE TABLE orders (
  order_id BIGSERIAL PRIMARY KEY,
  status   order_status NOT NULL DEFAULT 'pending'
);

Минус ENUM: добавить новое значение — ALTER TYPE, требующий блокировки. Удалить значение нельзя без пересоздания типа.

Альтернатива — справочная таблица:

CREATE TABLE order_statuses (
  code        TEXT PRIMARY KEY,
  label       TEXT NOT NULL,
  sort_order  INT
);

CREATE TABLE orders (
  order_id BIGSERIAL PRIMARY KEY,
  status   TEXT NOT NULL REFERENCES order_statuses(code) DEFAULT 'pending'
);

Справочная таблица гибче: добавление/удаление значений — обычный INSERT/DELETE без блокировок. Используйте ENUM только для действительно стабильных наборов (дни недели, стороны света).

JSONB: когда схема неизвестна заранее

JSONB хранит JSON в бинарном формате с поддержкой индексирования:

CREATE TABLE products (
  product_id BIGSERIAL PRIMARY KEY,
  name       TEXT NOT NULL,
  attributes JSONB -- гибкие атрибуты: цвет, размер, материал и т.д.
);

-- GIN-индекс для быстрого поиска внутри JSONB
CREATE INDEX idx_products_attributes ON products USING GIN (attributes);

-- Поиск по атрибуту
SELECT * FROM products
WHERE attributes @> '{"color": "red", "size": "XL"}';

JSON (без B) хранит исходный текст и медленнее. Используйте JSONB — всегда.

Итоговая шпаргалка

ЗадачаТип
Счётчик, возрастINTEGER
Большой ID, метрикиBIGINT
ДеньгиNUMERIC(15,2)
Любой текстTEXT
Фиксированный код (RU, USD)CHAR(2-3)
Дата и времяTIMESTAMPTZ
Только датаDATE
Флаг да/нетBOOLEAN
Глобальный IDUUID
Гибкие атрибутыJSONB

Правильный выбор типов — это инвестиция, которая окупается при масштабировании. Потренируйтесь проектировать схемы и писать запросы в нашем тренажёре SQL.

Похожие статьи

Попробуй на практике

Тренажёр с реальными задачами — бесплатно и без регистрации

Открыть тренажёр →