Ограничения и первичный ключ
Заставляем базу следить за корректностью данных с помощью ограничений.
Ограничение (constraint) — это правило на уровне таблицы, которое база проверяет автоматически и не даёт записать данные, нарушающие его.
PRIMARY KEY и rowid
Первичный ключ однозначно определяет строку. В SQLite у каждой обычной таблицы есть скрытый целочисленный столбец rowid. Если вы объявляете столбец как INTEGER PRIMARY KEY, он становится псевдонимом rowid — самым эффективным ключом, который к тому же автоматически нумеруется.
AUTOINCREMENT vs обычный rowid
Без ключевого слова AUTOINCREMENT SQLite нумерует строки сам: новый id обычно равен «максимальный + 1». Но если удалить последнюю строку, её номер может быть переиспользован. Слово AUTOINCREMENT запрещает повторное использование номеров — они только растут.
CREATE TABLE logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
msg TEXT
);
INSERT INTO logs (msg) VALUES ('старт'), ('работа');
DELETE FROM logs WHERE id = 2;
INSERT INTO logs (msg) VALUES ('после удаления');
SELECT id, msg FROM logs;
Вывод:
1|старт 3|после удаления
Новая строка получила id = 3, а не 2: благодаря AUTOINCREMENT освободившийся номер не используется повторно. В большинстве случаев AUTOINCREMENT не нужен — обычный INTEGER PRIMARY KEY и проще, и быстрее.
NOT NULL, UNIQUE, DEFAULT, CHECK
Остальные ограничения объявляют рядом со столбцом:
| Ограничение | Что гарантирует |
NOT NULL | значение обязательно, NULL запрещён |
UNIQUE | значения не повторяются |
DEFAULT x | значение по умолчанию, если не указано |
CHECK (условие) | значение должно удовлетворять условию |
Соберём «защищённую» таблицу аккаунтов: email обязателен и уникален, баланс по умолчанию 0 и не может быть отрицательным.
CREATE TABLE accounts (
id INTEGER PRIMARY KEY,
email TEXT UNIQUE NOT NULL,
balance INTEGER DEFAULT 0 CHECK (balance >= 0)
);
INSERT INTO accounts (email) VALUES ('[email protected]');
INSERT INTO accounts (email, balance) VALUES ('[email protected]', 500);
SELECT id, email, balance FROM accounts;
Вывод:
1|[email protected]|0 2|[email protected]|500
У первого аккаунта баланс взялся из DEFAULT 0, потому что мы его не указали.
Что будет при нарушении
Если попытаться записать данные, нарушающие правило, база отклонит операцию с ошибкой. Например, отрицательный баланс не пройдёт CHECK:
CREATE TABLE accounts (
id INTEGER PRIMARY KEY,
balance INTEGER CHECK (balance >= 0)
);
INSERT INTO accounts (balance) VALUES (-5);
Вывод:
Runtime error: CHECK constraint failed: balance >= 0
Аналогично попытка вставить дубликат в UNIQUE-столбец даст ошибку UNIQUE constraint failed, а NULL в NOT NULL-столбец — NOT NULL constraint failed. Ограничения — это страховка: лучше получить понятную ошибку при записи, чем испорченные данные потом.
Итог
INTEGER PRIMARY KEY— псевдонимrowid, эффективный автонумеруемый ключ.AUTOINCREMENTзапрещает переиспользование номеров; в большинстве случаев он не нужен.NOT NULL,UNIQUE,DEFAULT,CHECKзаставляют базу автоматически защищать данные.