PostgreSQLMiddleTechnical

Что такое enum и в чём его минус по сравнению с lookup-таблицей?

Enum фиксирует упорядоченный набор значений в типе PostgreSQL (легко сравнивать, компактно); lookup-таблица гибче — поддерживает FK, локализацию, управление из UI и удаление значений без DDL-миграции.

Enum в PostgreSQL

PostgreSQL enum — пользовательский тип данных с фиксированным упорядоченным набором строковых меток. Значения хранятся как 4-байтовый OID, занимают меньше места, чем text, и поддерживают сравнение и сортировку по порядку объявления.

-- Создать enum-тип и использовать в таблице
CREATE TYPE order_status AS ENUM ('new', 'paid', 'shipped', 'cancelled');

CREATE TABLE orders (
    order_id   bigserial PRIMARY KEY,
    user_id    bigint NOT NULL,
    status     order_status NOT NULL DEFAULT 'new',
    created_at timestamptz NOT NULL DEFAULT now()
);

-- Запрос с фильтром и сортировкой по enum-порядку
SELECT order_id, status
FROM orders
WHERE status > 'new'   -- работает по порядку объявления
ORDER BY status;

Изменение enum — DDL-операции

-- Добавить новое значение (PostgreSQL 9.1+, не требует rewrite таблицы):
ALTER TYPE order_status ADD VALUE 'refunded' AFTER 'cancelled';

-- Переименовать значение (PostgreSQL 10+):
ALTER TYPE order_status RENAME VALUE 'new' TO 'pending';

-- Удалить значение — НЕЛЬЗЯ напрямую!
-- Нужно: создать новый тип, мигрировать данные, дропнуть старый.

Lookup-таблица — альтернатива

-- Нормализованный вариант через reference table
CREATE TABLE order_statuses (
    status_code  text PRIMARY KEY,
    label_ru     text NOT NULL,
    label_en     text NOT NULL,
    sort_order   smallint NOT NULL,
    is_terminal  boolean NOT NULL DEFAULT false
);

INSERT INTO order_statuses VALUES
    ('new',       'Новый',    'New',       1, false),
    ('paid',      'Оплачен',  'Paid',      2, false),
    ('shipped',   'Отправлен','Shipped',   3, false),
    ('cancelled', 'Отменён',  'Cancelled', 4, true);

CREATE TABLE orders (
    order_id   bigserial PRIMARY KEY,
    user_id    bigint NOT NULL,
    status     text NOT NULL DEFAULT 'new'
                REFERENCES order_statuses(status_code),
    created_at timestamptz NOT NULL DEFAULT now()
);

Когда выбирать enum, когда lookup-таблицу

  • Enum — набор значений стабилен (технические состояния: pending/active/deleted), нет дополнительных атрибутов, не нужна локализация, изменение схемы синхронизируется с релизом кода.
  • Lookup-таблица — значения управляются через UI/admin-панель, нужны локализованные labels, sort_order, флаги (is_terminal, is_visible), история изменений или внешние ключи из других таблиц.

Подводные камни

  • Удалить значение из enum нельзя без создания нового типа и полной миграции таблицы — это блокирующая операция на продакшне.
  • Добавление значения через ADD VALUE не транзакционно до PostgreSQL 12 — откат транзакции не откатывает изменение типа.
  • Enum жёстко связывает релиз приложения и миграцию базы: добавили новое значение в enum — нужно деплоить код, умеющий его обрабатывать, одновременно.
  • ORM и клиентские библиотеки могут кэшировать список значений enum при старте — после ADD VALUE нужен рестарт приложения или очистка кэша соединений.
  • Enum не поддерживает FK к другой таблице — нельзя хранить дополнительные атрибуты на уровне базы без дополнительной таблицы.
  • Lookup-таблица добавляет JOIN к каждому запросу — для hot path с высокой нагрузкой может потребоваться кэширование справочника в приложении.
  • При использовании enum в партиционированной таблице изменение типа затрагивает все партиции — потенциально длительная операция.

Common mistakes

  • Использовать enum для бизнес-справочника.
  • Не учитывать сложность удаления значений.
  • Забывать про порядок enum-значений.

What the interviewer is testing

  • Просит пример стабильного enum.
  • Проверяет migration trade-off.
  • Уточняет, когда нужен FK на справочник.

Sources

Related topics