CHECK пропустил неизвестную цену: почему нужен NOT NULL

В таблице прайс-листа стоит CHECK (price > 0). Отрицательная цена действительно отклоняется, но после очередного импорта нашлась запись без цены. Ограничение выглядит достаточно строгим, поэтому хочется искать обход проверки в загрузчике.

В SQL сравнение NULL с числом даёт неизвестный результат. CHECK отклоняет строку, когда выражение ложно; неизвестное значение не является нарушением этого ограничения. Условие положительности и обязательность значения нужно выразить отдельно.

Как изменить решение

CREATE TABLE offer (
  id bigint PRIMARY KEY,
  price numeric(12, 2) NOT NULL CHECK (price > 0)
);
INSERT INTO offer VALUES (1, 100.00);
-- Оба следующих INSERT должны завершиться ошибкой:
INSERT INTO offer VALUES (2, NULL);
INSERT INTO offer VALUES (3, -1.00);

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

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

Что проверить на своих данных

Для существующей таблицы сначала посчитайте WHERE price IS NULL и WHERE price <= 0. Не исправляйте оба набора одним UPDATE без решения владельца данных. В тестах нужны положительная, нулевая, отрицательная и отсутствующая цена; проверка только отрицательного значения не ловит исходную проблему.

Та же логика важна для составных правил. CHECK (end_at > start_at) не требует наличия обеих дат. Если окончание может отсутствовать у открытого периода, это нормально, но start_at тогда стоит сделать обязательным. Ограничение должно описывать допустимые состояния предметной области, а не только удобное сравнение двух столбцов.

Практика дальше

Следующая практика — запросы, где правильность зависит от нескольких источников и граничных случаев. В курсе «Продвинутый SQL» вы решаете 109 задач на JOIN, окна и CTE, сверяете отчёты и собираете витрину интернет-магазина. Запросы выполняются в браузере на SQLite с автоматической проверкой.

Посмотреть программу и практику курса

Источник: CHECK пропустил неизвестную цену — официальная документация.

Другие задачи по теме: PostgreSQL: запись, блокировки и планы запросов.

Продолжить на практике