В PostgreSQL два столбца должны принимать только процентные значения от 0 до 100. Как спроектировать схему,...

В PostgreSQL два столбца должны принимать только процентные значения от 0 до 100. Как спроектировать схему, чтобы это правило задавалось один раз?

CREATE TABLE product (
    discount_percent NUMERIC
        CHECK (discount_percent BETWEEN 0 AND 100)
);

CREATE TABLE campaign (
    discount_percent NUMERIC
        CHECK (discount_percent BETWEEN 0 AND 100)
);
Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Для повторно используемого ограничения над скалярным значением в PostgreSQL можно создать пользовательский домен с ограничением CHECK, а затем использовать этот домен в обоих столбцах. Правило будет храниться в одном месте и автоматически проверяться при записи значений в столбцы этого типа.

CREATE DOMAIN percentage AS NUMERIC CHECK (VALUE BETWEEN 0 AND 100); CREATE TABLE product ( discount_percent percentage ); CREATE TABLE campaign ( discount_percent percentage );

VALUE внутри ограничения домена обозначает проверяемое значение. Если отсутствие значения запрещено, это нужно указать отдельно через NOT NULL у домена или конкретного столбца.

Исторический контекст

В реляционных схемах один и тот же смысловой тип может использоваться в разных таблицах: процент, код страны, денежная сумма или положительный идентификатор. Если описывать правило отдельным CHECK в каждом столбце, схема постепенно получает дублирование ограничений.

Домены предназначены для отделения общего набора допустимых значений от конкретных таблиц. Они решают проблему повторного описания скалярных правил, не создавая отдельную таблицу для каждого простого типа.

Постановка проблемы

В исходной схеме диапазон процента записан дважды. Если позже границы изменятся, разработчик должен найти и синхронно изменить все соответствующие ограничения; пропущенный столбец создаст различия в поведении таблиц.

Проверка только в приложении ненадёжна: данные могут поступать через другой сервис, административный SQL-клиент, импорт или миграцию. Дублирование CHECK надёжнее проверки в приложении, но всё равно создаёт риск расхождения правил.

Подробное решение

CREATE DOMAIN объявляет новый тип на основе существующего, в данном случае NUMERIC. Ограничение домена проверяется для любого столбца, объявленного этим доменом, поэтому изменение определения домена распространяется на все его использования после успешного изменения схемы.

CHECK домена должен отклонять недопустимые значения. Как и обычный CHECK в PostgreSQL, выражение с результатом NULL само по себе не считается нарушением, поэтому домен не запрещает NULL без дополнительного NOT NULL.

CREATE DOMAIN percentage AS NUMERIC NOT NULL CHECK (VALUE BETWEEN 0 AND 100); CREATE TABLE product ( discount_percent percentage ); CREATE TABLE campaign ( discount_percent percentage NOT NULL );

NOT NULL в определении домена делает отсутствие значения недопустимым для всех его применений. Если обязательность зависит от контекста, лучше оставить домен допускающим NULL, а NOT NULL задать только в нужных столбцах.

Домен подходит для скалярного инварианта, одинакового во всех местах использования. Он не заменяет внешние ключи и таблицы-справочники: если допустимые значения должны храниться как изменяемые данные, иметь описание, владельца или дополнительные атрибуты, предпочтительнее отдельная таблица и FOREIGN KEY.

Следует учитывать специфику СУБД: синтаксис и возможности доменов различаются между реализациями SQL. Поэтому в переносимом проекте нужно проверить поддержку доменов целевой СУБД либо использовать обычные CHECK и управлять их генерацией централизованным инструментом миграций.

Ситуация из практики

В платформе маркетинговых кампаний процент скидки используется в товарах, купонах и правилах для клиентов. Команда рассматривала три варианта.

Повторяющиеся CHECK были просты и переносимы, но при изменении диапазона требовали правки множества таблиц. Проверка в приложении не дублировала SQL, однако не защищала базу от прямых записей и импорта.

Таблица диапазонов или справочник допустимых значений давали управляемые данные, но были избыточны для непрерывного числового интервала и усложняли проверку. Был выбран домен percentage: правило является общим скалярным ограничением, а обязательность задаётся отдельно там, где она действительно необходима.

В результате новые таблицы используют единый тип, а изменение правила проходит как одна контролируемая миграция. Для бизнес-правил, зависящих от других строк или таблиц, команда отдельно применяет внешние ключи, уникальность или специализированные ограничения.

Что кандидаты часто упускают

  1. Запретит ли домен значение NULL только за счёт CHECK?

Нет. Если выражение VALUE BETWEEN 0 AND 100 получает NULL, результатом проверки также может быть NULL, а такой результат обычного CHECK в PostgreSQL не отклоняет строку. Для запрета отсутствия значения нужен NOT NULL в домене или столбце.

  1. Когда вместо домена нужна таблица-справочник с внешним ключом?

Таблица-справочник нужна, когда множество допустимых значений является данными: его состав меняется, значения имеют названия или дополнительные свойства, а также требуется управлять жизненным циклом записей. Домен лучше подходит для общего технического правила вроде диапазона, формата или знака числа.

  1. Достаточно ли домена, если правило зависит от других строк?

Нет. Домен проверяет отдельное значение и не предназначен для анализа других строк или таблиц. Например, правило о единственном активном бронировании, лимите записей на клиента или запрете пересекающихся интервалов требует другого механизма: уникального или специализированного ограничения, внешнего ключа, триггера либо иной гарантии на уровне СУБД.