Опирается на правила:
PG-T-050…PG-T-052,PG-M-020…PG-M-022из PostgreSQL Style Guide → раздел Enum, boolean и перечисления.
Важно знать
- Boolean — это
boolean, неsmallint 0/1, неvarchar('Y'/'N').- 3 способа перечисления: PG
ENUM, reference table,text + CHECK IN.ENUM— 4 байта, type-safety, но удаление значения нативно невозможно.- Reference table — 20 байт + JOIN, но full CRUD + дополнительные атрибуты.
CHECK IN— простой, без FK, до 5-7 значений.- Статусы доменных сущностей — обычно reference table (растут, приобретают атрибуты).
- Java —
enum, неString.@Enumerated(EnumType.STRING)для JPA.- status vs lifecycle — переходы это отдельная state machine, не тип.
Перечислимые значения — статус заказа, тип уведомления, валюта, роль пользователя. UCP формулирует три способа и таблицу выбора между ними.
Boolean — boolean
PG-T-050:
-- ✓
is_active boolean NOT NULL DEFAULT true,
is_verified boolean NOT NULL DEFAULT false
-- ✗
is_active smallint NOT NULL DEFAULT 1 CHECK (is_active IN (0, 1)),
is_active char(1) NOT NULL DEFAULT 'Y' CHECK (is_active IN ('Y', 'N'))
boolean:
- 1 байт, атомарный.
- Короче в SQL:
WHERE is_activevsWHERE is_active = 1. - Правильно мапится на Java
boolean.
Три способа
Пример: статус заказа NEW, PAID, SHIPPED, DELIVERED, CANCELLED.
Вариант 1: PG ENUM
CREATE TYPE order_status AS ENUM ('NEW', 'PAID', 'SHIPPED', 'DELIVERED', 'CANCELLED');
CREATE TABLE order_doc (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
status order_status NOT NULL DEFAULT 'NEW'
);
Плюсы:
- Компактно (4 байта на значение).
- Type-safety на уровне БД.
- Хорошо смотрится в
EXPLAIN,WHERE status = 'PAID'.
Минусы:
- Добавление в PG12+ через
ALTER TYPE ... ADD VALUE. До 12 — пересоздание типа. - Удаление значения — нативно невозможно. Только пересоздание типа со всеми зависимостями = большая миграция.
- Порядок влияет на
ORDER BY status, изменение порядка невозможно. - Переименование — PG10+ через
RENAME VALUE. - Между сервисами/репликами enum-ы должны совпадать.
Вариант 2: Reference table
CREATE TABLE order_status_dict (
code varchar(20) PRIMARY KEY,
description text NOT NULL,
sort_order smallint NOT NULL,
is_terminal boolean NOT NULL
);
INSERT INTO order_status_dict VALUES
('NEW', 'Создан', 10, false),
('PAID', 'Оплачен', 20, false),
('SHIPPED', 'Отправлен', 30, false),
('DELIVERED', 'Доставлен', 40, true),
('CANCELLED', 'Отменён', 50, true);
CREATE TABLE order_doc (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
status varchar(20) NOT NULL REFERENCES order_status_dict(code) DEFAULT 'NEW'
);
Плюсы:
- Full CRUD на значения:
INSERT/UPDATE/DELETE. - Миграция данных без
ALTER TYPE(UPDATE order SET status = 'X' WHERE status = 'OLD'). - Дополнительные атрибуты (
description,sort_order,is_terminal) рядом. - Удобно админке — справочник без деплоя.
- FK защищает от опечаток.
Минусы:
- 20 байт на значение (
varchar(20)) против 4 у enum. - При больших таблицах — лишний JOIN на каждом
SELECT(часто решается денормализацией).
Вариант 3: text + CHECK IN
CREATE TABLE order_doc (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
status text NOT NULL DEFAULT 'NEW'
CHECK (status IN ('NEW', 'PAID', 'SHIPPED', 'DELIVERED', 'CANCELLED'))
);
Плюсы:
- Никаких типов. Миграция =
ALTER TABLE ... DROP CONSTRAINT ... ADD CONSTRAINT .... - Не нужен справочник.
Минусы:
- Нет FK, нельзя ссылаться из других таблиц по логике перечисления.
- Дополнительные атрибуты — отдельно.
- 10+ значений в
CHECK IN— некрасиво.
Когда что брать
PG-T-051:
| Случай | Выбор |
|---|---|
| Известный, редко меняется, без атрибутов | ENUM |
| Может расти, переименовываться, нужны атрибуты | reference table |
| Простой, до 5-7 значений, без атрибутов | CHECK IN |
| Часто меняется в админке | reference table |
Практика:
- Статусы доменных сущностей (
order_status,payment_status) — reference table. Приобретают атрибуты со временем (is_terminal,sort_order). - Технические перечисления (
event_kind,notification_channel) —ENUMилиCHECK IN. - 2-3-буквенные коды (страна, валюта, язык) — reference table со стандартами (ISO 3166, 4217, 639).
Миграционные ловушки ENUM
ADD VALUE — мгновенно но в отдельной tx
PG-M-020:
ALTER TYPE order_status ADD VALUE 'PARTIALLY_REFUNDED';
Важно: в той же транзакции, где добавили значение, его нельзя сразу использовать:
BEGIN;
ALTER TYPE order_status ADD VALUE 'PARTIALLY_REFUNDED';
INSERT INTO order_doc (status) VALUES ('PARTIALLY_REFUNDED'); -- ОШИБКА
COMMIT;
Миграция enum и миграция данных — в разных changeset Liquibase.
RENAME VALUE — PG10+
PG-M-021:
ALTER TYPE order_status RENAME VALUE 'OLD_NAME' TO 'NEW_NAME';
DROP VALUE — невозможно нативно
PG-M-022: замена enum через несколько релизов.
- Создать новый тип (
order_status_v2) с нужным набором. - Добавить временную колонку с новым типом.
- Backfill: маппинг старых значений в новые.
- Дропнуть старую колонку, переименовать новую.
- Дропнуть старый тип.
Две-три отдельные миграции с релизом приложения между ними. Если предполагается удаление значений — берите reference table сразу.
Java-сторона
PG-T-052: enum, не String.
public enum OrderStatus {
NEW, PAID, SHIPPED, DELIVERED, CANCELLED
}
// JPA
@Enumerated(EnumType.STRING)
private OrderStatus status;
// jOOQ — генерирует enum по типу из БД (если PG ENUM)
// либо явный конвертер для reference table / CHECK IN
Для reference table / CHECK IN — колонка text/varchar, в Java enum через EnumType.STRING или jOOQ конвертер.
status vs lifecycle
Не любое поле «один из набора» — это enum. Для статусов с переходами по правилам (NEW → PAID → SHIPPED, но не NEW → SHIPPED) одного типа недостаточно. Тип решает «какие значения вообще валидны», переходы — отдельная state machine в коде (см. DDD → aggregate).
Что запрещено
| Антипаттерн | Правило | Что взамен |
|---|---|---|
Boolean как smallint 0/1 | PG-T-050 | boolean |
Boolean как char(1) 'Y'/'N' | PG-T-050 | boolean |
ENUM для часто меняющегося набора | PG-T-051 | reference table |
CHECK IN (...) с 10+ значениями | PG-T-051 | reference table |
| ADD VALUE + использование в одной tx | PG-M-020 | разные changeset |
| Удаление enum-значения через UPDATE → DROP | PG-M-022 | новый тип + миграция |
Java String для status | PG-T-052 | enum |
| Reference table без FK | PG-T-051 | REFERENCES order_status_dict(code) |
| Тип-уровень для state machine переходов | без правила | отдельный domain code |
Куда дальше
- PG → Enum, boolean и перечисления — нормативные формулировки.
- Числа и точность —
booleanдетали. - Строковые типы —
varchar(20)для кодов. - JSONB — когда оправдан, когда нет — для сложных enum-объектов.
- Миграции и breaking changes без даунтайма — drop enum value.
- DDD → aggregate — state machine для переходов.
- Антипаттерны типов — сводка.