Ограничения в SQL: короткий гайд для PostgreSQL 16
Добавьте в PostgreSQL 16 проверки, которые не дадут сохранить пустой email, отрицательную сумму или заказ без покупателя. За несколько шагов создадим две связанные таблицы и проверим ограничения на данных.

В PostgreSQL 16 ограничения не дают сохранить строку, которая нарушает правила данных: например, заказ с отрицательной суммой или ссылкой на несуществующего покупателя. Ниже — короткая инструкция: за 10–15 минут вы создадите две связанные таблицы, добавите ограничения и проверите результат.
Что нужно до начала
Нужна база PostgreSQL 16 и доступ к ней с правом создавать таблицы. Запросы можно выполнить в любом SQL-клиенте; команда \d, которая встретится дальше, работает в консольном клиенте psql. Пример рассчитан на пустую схему public, где ещё нет таблиц customers и orders.
Термин. Ограничение — правило на уровне базы данных: если новая или изменённая строка его нарушает, PostgreSQL отклонит запрос. Это защита не только от ошибок в приложении: то же правило сработает для любого сервиса, который пишет в эту базу.
Добавляем ограничения по шагам
Создайте таблицу покупателей. Первичный ключ идентифицирует запись, email обязателен и не может повторяться, а возраст должен быть не меньше 18. Возраст при этом можно не указывать: условие
CHECKсамо по себе не запрещаетNULL.CREATE TABLE customers ( id bigint GENERATED ALWAYS AS IDENTITY, email text NOT NULL, age integer, CONSTRAINT customers_pk PRIMARY KEY (id), CONSTRAINT customers_email_unique UNIQUE (email), CONSTRAINT customers_age_adult CHECK (age >= 18) );Проверка: в psql выполните
\d customers. В описании таблицы должны быть видны первичный ключ, уникальность email и проверка возраста. В SQL-клиенте вместо команды psql можно открыть структуру таблицы.Создайте таблицу заказов. Каждый заказ должен ссылаться на существующего покупателя, иметь неотрицательную сумму и один из двух допустимых статусов. Если статус не передать, база подставит
new.CREATE TABLE orders ( id bigint GENERATED ALWAYS AS IDENTITY, customer_id bigint NOT NULL, amount numeric(10, 2) NOT NULL, status text NOT NULL DEFAULT 'new', CONSTRAINT orders_pk PRIMARY KEY (id), CONSTRAINT orders_customer_fk FOREIGN KEY (customer_id) REFERENCES customers (id), CONSTRAINT orders_amount_nonnegative CHECK (amount >= 0), CONSTRAINT orders_status_allowed CHECK (status IN ('new', 'paid')) );Проверка: выполните
\d orders. Должны отображаться внешний ключ наcustomers, проверки суммы и статуса, а также значение статуса по умолчанию.Вставьте допустимые данные. Сначала добавьте покупателя, затем заказ. Для простоты пример предполагает, что это первые записи в таблицах: значение
idу покупателя будет 1.INSERT INTO customers (email, age) VALUES ('reader@example.com', 25); INSERT INTO orders (customer_id, amount) VALUES (1, 1490.00); SELECT o.id, c.email, o.amount, o.status FROM orders AS o JOIN customers AS c ON c.id = o.customer_id;Проверка: запрос должен вернуть заказ с суммой
1490.00и статусомnew. Если таблица уже содержит записи, подставьте фактическийidпокупателя из запросаSELECT id, email FROM customers;.Проверьте, какие правила созданы. Представление
information_schema.table_constraintsпоказывает ограничения таблиц, включая первичные и внешние ключи, уникальность и проверки.SELECT table_name, constraint_name, constraint_type FROM information_schema.table_constraints WHERE table_schema = 'public' AND table_name IN ('customers', 'orders') ORDER BY table_name, constraint_name;Проверка: в результате должны быть ограничения с именами
customers_pk,customers_email_unique,orders_customer_fkи другими из примера. ОграниченияNOT NULLпроверяются отдельно: попробуйте добавить строку без обязательного значения только в тестовой базе.
Какое ограничение за что отвечает
| Ограничение | Что проверяет | Пример из схемы |
|---|---|---|
NOT NULL | Значение обязательно должно быть указано | Email покупателя, сумма заказа |
UNIQUE | Значение не должно повторяться | Email покупателя |
PRIMARY KEY | Уникально идентифицирует каждую строку; значение не может быть пустым | Идентификатор покупателя или заказа |
FOREIGN KEY | Ссылка должна вести на существующую строку в другой таблице | Покупатель в заказе |
CHECK | Значение должно удовлетворять условию | Возраст от 18 лет, сумма неотрицательная |
DEFAULT | Подставляет значение, если его не передали | Статус нового заказа |
Если правило важно для целостности данных, зафиксируйте его в базе, а не только в форме или коде приложения. Проверка на клиенте улучшает интерфейс, но не защищает базу от другого сервиса или прямого SQL-запроса.
Если запрос не проходит
null value in column ... violates not-null constraint. Вы не передали значение для поля сNOT NULL. Укажите значение в запросе или проверьте, что столбцу задан подходящийDEFAULT.duplicate key value violates unique constraint. Такой email уже сохранён. Найдите существующую запись и решите, нужно ли обновить её или использовать другой адрес.violates check constraint. Значение не прошло проверку: например, сумма отрицательная или возраст меньше 18. Сверьте данные с условием в сообщении об ошибке и именем ограничения.violates foreign key constraint. В заказе указанcustomer_id, которого нет в таблице покупателей. Сначала создайте покупателя или используйте его существующий идентификатор.relation ... already exists. Таблица с таким именем уже создана. Не добавляйте к инструкцииDROP TABLEвслепую: удаление таблицы может уничтожить данные. Проверьте текущую схему и при необходимости используйте отдельную тестовую базу.
Что важно учесть в рабочей базе
Ограничения защищают данные, но сами по себе не описывают все бизнес-правила. Например, допустимые статусы в примере перечислены явно; при добавлении нового статуса проверку придётся изменить. Если правила сложнее, не пытайтесь уместить всю логику в один длинный CHECK: разделите ограничения на понятные именованные условия.
Внешний ключ не удаляет связанные записи автоматически. В примере действие при удалении покупателя не задано, поэтому база не позволит удалить его, пока на него ссылаются заказы. Это безопаснее, чем случайно потерять историю заказов; поведение при удалении стоит выбрать осознанно.
В ограничениях CHECK есть важная особенность: результат условия NULL не считается нарушением. Поэтому для обязательного значения, например суммы, нужны оба правила — NOT NULL и CHECK (amount >= 0). Одно не заменяет другое.
Что делать дальше
Повторите пример в тестовой базе, затем добавьте ограничения к своей схеме по одному и проверьте, какие существующие строки им не соответствуют. Перед изменениями рабочей базы сделайте резервную копию, оцените объём таблиц и планируйте проверку ограничений с учётом нагрузки. Следующий шаг — изучить, как PostgreSQL добавляет ограничения к уже заполненной таблице и что происходит с внешними ключами при изменении связанных данных.
Проверка рекламы на риски: Что в макете вызовет вопросы ФАС: превосходные степени, сравнения, обещания результата, маркировка.
Спросить за 40 ₽Источники: PostgreSQL 16: ограничения
Частые вопросы
Обязательно ли дублировать проверки и в приложении, и в базе?
Почему CHECK (age >= 18) пропускает пустой возраст?
Что будет, если удалить покупателя, на которого ссылается заказ?
Подойдёт ли этот пример для другой версии PostgreSQL?
Что делать, если INSERT неожиданно завершился ошибкой?
Можно ли использовать DEFAULT вместо NOT NULL?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.