Типы данных
Зачем типы
Правильный тип:
- Экономит место — int вместо text.
- Ускоряет запросы — сравнения и индексы.
- Ловит ошибки — нельзя записать строку в число.
- Документирует — структура видна из схемы.
PostgreSQL строже MySQL. Не даст вставить 'abc' в integer.
Числовые типы
Целые:
| Тип | Размер | Диапазон |
|---|---|---|
smallint | 2 байта | -32 768 … 32 767 |
integer (int, int4) | 4 байта | -2.1 млрд … 2.1 млрд |
bigint (int8) | 8 байт | ±9.2 квинтиллиона |
Правило: id — bigint. Счётчики — integer. Флаги — smallint или boolean.
Автоинкремент:
-- Старый способ
id SERIAL PRIMARY KEY -- integer
id BIGSERIAL PRIMARY KEY -- bigint
-- Современный (PostgreSQL 10+)
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY
Дробные:
| Тип | Точность | Для чего |
|---|---|---|
real (float4) | 6 знаков | научные расчёты |
double precision (float8) | 15 знаков | координаты |
numeric(p, s) | точно | деньги |
Деньги — только numeric:
price NUMERIC(10, 2) NOT NULL
-- 12345678.90
⚠️ float для денег — потеря точности. 0.1 + 0.2 != 0.3.
Строковые типы
| Тип | Особенности |
|---|---|
char(n) | фиксированная длина, добивает пробелами |
varchar(n) | переменная, макс n |
varchar | переменная, без лимита |
text | переменная, без лимита |
Правило:
text— по умолчанию (в PostgreSQL он не медленнее).varchar(n)— если нужен бизнес-лимит (email ≤ 255).char(n)— почти никогда.
email VARCHAR(255) NOT NULL,
name TEXT NOT NULL,
code CHAR(2) -- страна: US, RU
Дата и время
| Тип | Особенности |
|---|---|
date | только дата |
time | только время |
timestamp | без таймзоны |
timestamptz | с таймзоной |
interval | промежуток |
Правило: всегда timestamptz. Хранит в UTC, отдаёт в таймзоне клиента.
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
⚠️ timestamp без зоны — источник багов между серверами.
Интервалы:
SELECT NOW() + INTERVAL '1 day 2 hours';
SELECT '2026-01-01'::date - '2025-12-25'::date; -- 7
Логический
is_active BOOLEAN NOT NULL DEFAULT TRUE
Значения: TRUE, FALSE, NULL. Не 0/1.
UUID
Встроенный с 13 версии:
id UUID PRIMARY KEY DEFAULT gen_random_uuid()
Плюсы:
- Не пересекается при merge.
- Не угадывается в URL.
- Удобно в распределённых системах.
Минусы:
- 16 байт vs 8.
- Хуже для индекса (случайный порядок).
Правило: для публичных id (в URL) — UUID. Для внутренних FK — bigint.
JSON / JSONB
| Тип | Особенности |
|---|---|
json | хранит как текст, не индексируется |
jsonb | бинарный, индексируется, быстрее |
Правило: всегда jsonb.
metadata JSONB NOT NULL DEFAULT '{}'::jsonb
Запросы:
-- Достать поле
SELECT metadata->>'theme' FROM users;
-- Проверить наличие ключа
SELECT * FROM users WHERE metadata ? 'theme';
-- Поиск по вложенному
SELECT * FROM users WHERE metadata->'prefs'->>'lang' = 'ru';
-- Индекс
CREATE INDEX idx_users_metadata_theme ON users ((metadata->>'theme'));
Когда использовать:
- Динамические поля (настройки, метаданные).
- Внешние API (сырые ответы).
- Не для бизнес-логики (там — колонки).
Массивы
tags TEXT[] NOT NULL DEFAULT '{}',
scores INTEGER[]
Операции:
INSERT INTO posts (tags) VALUES (ARRAY['php', 'laravel']);
SELECT * FROM posts WHERE 'php' = ANY(tags);
SELECT tags[1] FROM posts; -- первый элемент (индексы с 1)
SELECT array_length(tags, 1) FROM posts;
Индекс:
CREATE INDEX idx_posts_tags ON posts USING GIN(tags);
Когда:
- Список тегов.
- Небольшие массивы.
- Не для связи многие-ко-многим (там таблица).
ENUM
CREATE TYPE order_status AS ENUM ('pending', 'paid', 'shipped', 'cancelled');
CREATE TABLE orders (
status order_status NOT NULL DEFAULT 'pending'
);
Плюсы: валидация на уровне БД. Минусы: тяжело менять (нельзя удалить значение).
Альтернатива — lookup-таблица:
CREATE TABLE order_statuses (code TEXT PRIMARY KEY);
INSERT INTO order_statuses VALUES ('pending'), ('paid');
Правило: для стабильных списков — ENUM. Для изменяемых — таблица.
Специальные
Сетевые:
inet, cidr, macaddr
Гео:
point, line, polygon -- встроенные
geometry -- PostGIS
Полнотекстовый поиск:
tsvector, tsquery
Диапазоны:
int4range, tstzrange, numrange
Пример:
CREATE TABLE bookings (
period TSTZRANGE NOT NULL,
EXCLUDE USING GIST (period WITH &&) -- запрет пересечений
);
Пример: таблица пользователей
CREATE TABLE users (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
uuid UUID NOT NULL DEFAULT gen_random_uuid() UNIQUE,
email VARCHAR(255) NOT NULL UNIQUE,
name TEXT NOT NULL,
password_hash TEXT NOT NULL,
balance NUMERIC(12, 2) NOT NULL DEFAULT 0,
metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
tags TEXT[] NOT NULL DEFAULT '{}',
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
Приведение типов
SELECT '123'::integer; -- 123
SELECT 123::text; -- '123'
SELECT '2026-01-01'::date;
SELECT CAST('123' AS INTEGER);
-- Проверка типа
SELECT pg_typeof(123);
Правило: явное приведение — всегда лучше неявного.
Частые ошибки
1. varchar(255) по привычке:
В PostgreSQL text не медленнее
Решение: использовать text.
2. float для денег:
0.1 + 0.2 = 0.30000000000000004
Решение: numeric(12,2).
3. timestamp без зоны:
Сервер в UTC, клиент в MSK — разница в 3 часа
Решение: timestamptz.
4. JSON вместо колонок:
Нельзя сделать FK, тяжело индексировать
Решение: бизнес-данные — колонки. JSON — для метаданных.
5. Serial с большим ID:
int переполнится на 2.1 млрд
Решение: bigint + identity.
6. ENUM для изменяемых значений:
Нельзя удалить значение из ENUM
Решение: lookup-таблица.
Практика
1. Создайте таблицу products:
CREATE TABLE products (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
sku VARCHAR(64) NOT NULL UNIQUE,
name TEXT NOT NULL,
price NUMERIC(10, 2) NOT NULL CHECK (price >= 0),
stock INTEGER NOT NULL DEFAULT 0 CHECK (stock >= 0),
attributes JSONB NOT NULL DEFAULT '{}'::jsonb,
tags TEXT[] NOT NULL DEFAULT '{}',
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
2. Вставьте товар с JSON-атрибутами и тегами.
3. Запросите по атрибуту attributes->>'color' = 'red'.
4. Запросите по тегу 'sale' = ANY(tags).
Итог
- Целые:
smallint,integer,bigint. - Дробные:
numericдля денег. - Строки:
textпо умолчанию. - Дата:
timestamptzвсегда. - UUID: для публичных id.
- JSONB: для метаданных.
- Массивы: для тегов.
- ENUM: для стабильных списков.
Дальше — создание таблиц и схемы.