CHECK и NOT NULL: два разных обещания таблицы

2 мин чтения

Почему CHECK (price > 0) допускает NULL и как вместе с NOT NULL задать обязательную положительную цену.

Две проверки для обязательной цены
  1. NOT NULLЗапрещает отсутствие значения
  2. CHECK (price > 0)Отклоняет ноль и отрицательные числа
  3. Оба ограниченияЦена заполнена и больше нуля

Цена должна быть известна и больше нуля. Добавляем CHECK (price > 0), но строка с NULL всё равно сохраняется. Это не ошибка PostgreSQL: сравнение с NULL не возвращает ложь. Для обязательного значения нужно второе ограничение — NOT NULL. Проверим оба правила на небольшой таблице.

Два независимых ограничения

CREATE TABLE demo_prices (
    sku text PRIMARY KEY,
    price numeric(12,2) NOT NULL,
    CONSTRAINT positive_price CHECK (price > 0)
);
INSERT INTO demo_prices VALUES ('book', 120.00);
SELECT sku, price FROM demo_prices;

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

Проверяем отрицательные случаи

-- Выполняйте каждую команду отдельно, вне явной транзакции.
INSERT INTO demo_prices VALUES ('bad-zero', 0);
INSERT INTO demo_prices VALUES ('bad-null', NULL);
SELECT count(*) FROM demo_prices;

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

Для каких правил подходит CHECK

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

Перед добавлением ограничений найдите старые нулевые цены и пропуски. Их исправление согласуйте с теми, кто отвечает за данные: заменить неизвестную цену нулём — значит изменить смысл записи. После миграции проверьте определение таблицы, правильную вставку и оба отказа. Правило должно работать и при записи в обход формы.

Источники и дальнейшее чтение