CHECK в SQLite позволяет валидировать данные прямо на уровне таблицы. Это удобный способ отсеивать некорректные значения ещё до того, как они попадут в базу. Ограничение работает при INSERT и UPDATE, поэтому хорошо подходит для бизнес-правил, которые должны соблюдаться независимо от прикладного кода

Как работает CHECK в SQLite
SQLite вычисляет выражение внутри CHECK каждый раз при изменении строки. Если выражение возвращает 0, операция прерывается с ошибкой ограничения. Если результат ненулевой или NULL, строка считается допустимой
Это важно на практике: CHECK не заменяет типизацию, но помогает жёстче контролировать данные в тех местах, где одного NOT NULL или UNIQUE недостаточно
По этой теме полезно отдельно посмотреть EXPLAIN QUERY PLAN: план выполнения SQL-запроса в SQLite, чтобы расширить контекст и сравнить подходы
По этой теме полезно отдельно посмотреть Создание Flutter-приложения с SQLite, BLoC и Streams, чтобы расширить контекст и сравнить подходы
Синтаксис ограничения CHECK
Ограничение можно задать на уровне столбца:
CREATE TABLE products (
id INTEGER PRIMARY KEY,
price REAL CHECK (price >= 0)
);
Или на уровне всей таблицы:
CREATE TABLE employees (
id INTEGER PRIMARY KEY,
salary REAL,
bonus REAL,
CHECK (salary >= 0 AND bonus >= 0)
);
Первый вариант удобен для простых проверок одного поля, второй — для правил, которые зависят сразу от нескольких столбцов
Пример проверки на уровне столбца
Допустим, в таблице контактов возраст не должен быть меньше 18:
CREATE TABLE contacts (
contact_id INTEGER PRIMARY KEY,
first_name TEXT NOT NULL,
last_name TEXT NOT NULL,
age INTEGER NOT NULL CHECK (age >= 18)
);
Корректная вставка пройдёт:
INSERT INTO contacts(first_name, last_name, age)
VALUES ('Ivan', 'Petrov', 29);
А некорректная завершится ошибкой:
INSERT INTO contacts(first_name, last_name, age)
VALUES ('Anna', 'Smirnova', 16);
Такой подход полезен, когда правило должно выполняться всегда, даже если в приложение добавятся новые точки записи данных
Пример проверки на уровне таблицы
Теперь пример, где условие связано с несколькими полями:
CREATE TABLE products (
product_id INTEGER PRIMARY KEY,
list_price REAL NOT NULL,
discount REAL NOT NULL DEFAULT 0,
CHECK (list_price >= discount AND discount >= 0)
);
Здесь ограничение гарантирует сразу две вещи:
- скидка не может быть отрицательной
- скидка не может быть больше базовой цены
Это уже типичный production-кейс, потому что подобные ошибки часто проскакивают в импортерах, административных формах и интеграциях
Что важно знать про ограничения CHECK
Есть несколько особенностей:
- выражение не может содержать подзапрос
CHECKне исправляет данные, а только блокирует неверную запись- сложные бизнес-правила лучше держать простыми и проверяемыми
Если правило становится слишком громоздким, его стоит либо упростить, либо вынести в приложение и оставить в SQLite только базовые инварианты
Как добавить CHECK в существующую таблицу
В SQLite нельзя просто выполнить ALTER TABLE ... ADD CHECK ... в привычном виде. Обычно применяется стандартная схема миграции:
- создать новую таблицу с нужным
CHECK - перенести данные
- удалить старую таблицу
- переименовать новую
Пример:
CREATE TABLE products_new (
product_id INTEGER PRIMARY KEY,
list_price REAL NOT NULL,
discount REAL NOT NULL DEFAULT 0,
CHECK (list_price >= discount AND discount >= 0)
);
INSERT INTO products_new(product_id, list_price, discount)
SELECT product_id, list_price, discount
FROM products;
DROP TABLE products;
ALTER TABLE products_new RENAME TO products;
Перед такой миграцией полезно сначала проверить данные, иначе перенос остановится на первой строке, нарушающей новое правило
Когда использовать CHECK в реальном проекте
Ограничение особенно полезно для таких правил:
- возраст не меньше минимального порога
- цена и скидка не противоречат друг другу
- дата окончания не раньше даты начала
- количество не уходит в отрицательные значения
Если такие условия важны для целостности данных, лучше продублировать их в самой базе, а не надеяться только на код приложения
Частые ошибки
Первая ошибка — пытаться использовать CHECK для очень сложной предметной логики. Чем проще и прозрачнее выражение, тем легче сопровождать схему
Вторая ошибка — считать, что NULL всегда ведёт к ошибке. В SQLite выражение CHECK, вернувшее NULL, не считается нарушением, и это нужно учитывать при проектировании
Третья ошибка — добавлять ограничение в существующую таблицу без предварительной проверки старых данных
CHECKв SQLite — это простой и полезный инструмент для валидации данных на уровне схемы. Его стоит использовать там, где правило должно соблюдаться всегда и не зависеть от конкретного клиента, формы или скрипта импорта



