Это замечательный вопрос о защите целостности данных. Оставлять поле просто как “свободный текст на 20 символов” действительно небезопасно — скрипт может случайно записать туда “time out” вместо “timeout”, и аналитика сломается.
Давай разберем варианты хранения таких перечислений (categorical data) в базе данных и их trade-offs.
Вариант 1: Короткие коды (CHAR(2) или SMALLINT)
Например, ‘KC’ (King Captured), ‘TO’ (Timeout), ‘RE’ (Resign).
- Плюсы: Занимает буквально 1-2 байта. Самый компактный вариант.
- Минусы: Ужасный Developer Experience (удобство для разработчика). Через два года ты откроешь базу, увидишь код
'DD'и будешь мучительно вспоминать: это Double Declined, Draw Declined или Disconnected? Чтобы сделать красивый отчет, тебе всегда придется делать маппинг этих кодов в приложении или создавать отдельную таблицу-справочник и постоянно делать к нейJOIN. В современной аналитике от этого уходят.
Вариант 2: Текст с ограничением (VARCHAR(20) + CHECK Constraint)
Это то, как обычно делают в большинстве современных проектов, если набор значений редко меняется.
termination VARCHAR(20)
CHECK (termination IN (
'king_captured',
'timeout',
'resignation',
'draw_agreement',
'double_declined',
'aborted'
))- Плюсы: Данные абсолютно читаемы (ты сразу видишь ‘timeout’ в консоли). Защита от опечаток 100% — если скрипт попытается вставить ‘time out’, база выбросит ошибку.
- Минусы: Занимает чуть больше места (около 8-15 байт на строку), но, как мы обсуждали ранее, это капля в море. Если нужно добавить новую причину, нужно сделать
ALTER TABLEи обновить правилоCHECK.
Вариант 3: Нативный тип ENUM в PostgreSQL (Золотая середина)
PostgreSQL имеет специальный тип данных для таких случаев — Перечисления (ENUM).
Мы сначала создаем свой собственный тип в базе:
CREATE TYPE termination_reason AS ENUM (
'king_captured',
'timeout',
'resignation',
'draw_agreement',
'double_declined',
'aborted'
);А затем в таблице games пишем:
termination termination_reason- Плюсы: Это магия PostgreSQL. Физически на жестком диске база хранит это как маленькое число (4 байта), что сверхэффективно для индексов и диска. Но когда ты делаешь
SELECT, база автоматически показывает тебе красивый текст'timeout'. Вставить “левый” текст невозможно по определению типа. - Минусы: В PostgreSQL легко добавить новое значение в ENUM (
ALTER TYPE ... ADD VALUE 'server_crash'), но очень сложно удалить существующее значение (приходится пересоздавать тип). Впрочем, для причин окончания шахматной партии удаление значений вряд ли потребуется.
Почему я написал просто VARCHAR(20) в плане?
В первоначальных черновиках архитектуры я часто оставляю простые VARCHAR, чтобы не перегружать схему сложными специфичными для Postgres конструкциями, пока мы не утвердим бизнес-логику.
Но ты абсолютно прав: оставлять его без ограничений нельзя.
Моя рекомендация: Использовать нативный PostgreSQL ENUM. Это даст нам строгую валидацию, минимальный размер на диске (4 байта) и максимальную читаемость для аналитики.
Согласен ли ты использовать ENUM для этого поля (а также, возможно, для поля mode — ‘classic’, ‘x2’)?