Тема: Средства проектирования структур БД: ограничения (§2.2 с.7–8 учебной программы). Вид занятия: Урок с объяснением теории. 2 академических часа.
Ограничения целостности (Data Integrity Constraints) — это фундаментальный каркас реляционной модели, гарантирующий корректность данных на физическом и логическом уровне независимо от клиентского программного обеспечения. На занятии 22 студенты переходят от абстрактной нормализации к практическому инструментарию декларативной целостности современных промышленных СУБД (PostgreSQL / ANSI SQL-92/99/2011).
CHECK с применением POSIX регулярных выражений (операторы ~, ~*, !~, !~*).tsrange, daterange, numrange) и проектирование ограничений взаимного исключения EXCLUDE USING gist с оператором пересечения && для предотвращения овербукинга и конфликтов расписания.DEFERRABLE INITIALLY DEFERRED / IMMEDIATE при циклических графах зависимостей (проблема «курицы и яйца»).Материал спроектирован для двух вариантов регламента: основного (80 минут) и сокращенного (60 минут).
| Этап занятия | Основной регламент (80 мин) | Сокращенный регламент (60 мин) | Методическое содержание и акценты внимания |
|---|---|---|---|
| 1. Оргмомент и мотивационный кейс | 5 минут | 3 минуты | Приветствие, проверка готовности. Разбор инцидента: двойное бронирование VIP-переговорной в крупной корпорации из-за наивной проверки SELECT COUNT(*) WHERE ... в веб-приложении при параллельных транзакциях. Формулировка проблемы: почему проверки на бэкенде недостаточно? |
| 2. Актуализация опорных знаний | 8 минут | 5 минут | Блиц-опрос: понятие целостности сущностей (Entity Integrity), ссылочной целостности (Referential Integrity), разница между первичным и уникальным ключом, поведение NULL при уникальности. |
| 3. Изложение нового материала | 35 минут | 27 минут |
Пошаговое раскрытие четырех ключевых тем: 1) Домены и CHECK с regex (~); 2) Интервальные типы и EXCLUDE USING gist (tsrange, &&); 3) Циклические ссылки и отложенные FK (DEFERRABLE); 4) Логические коллизии и противоречия предикатов. |
| 4. Разбор эталонного сквозного кейса | 12 минут | 10 минут | Преподаватель в реальном времени демонстрирует на проекторе выполнение DDL-скрипта с созданием расширения btree_gist, таблицы бронирования с EXCLUDE и проверку параллельной вставки конфликтующего интервала, а также создание циклической пары таблиц Отель-Номер через DEFERRABLE. |
| 5. Первичное закрепление (рабочий лист) | 14 минут | 11 минут | Самостоятельная работа студентов в интерактивном рабочем листе zanyatie.html по 4 вариантам. Анализ требований, составление DDL-ограничений, поиск логических ошибок. Преподаватель выступает тьютором. |
| 6. Подведение итогов, рефлексия, ДЗ | 6 минут | 4 минуты | Анализ типичных ошибок, демонстрация тематического мема (Слайд 15), выставление предварительных баллов по 4-балльной рубрике, инструктаж по резервному заданию domashnee_zadanie.html. |
Современная реляционная СУБД реализует трехуровневую пирамиду декларативной защиты:
Домен представляет собой пользовательский тип данных на базе стандартного с навешенным инвариантом CHECK. Позволяет повторно использовать строгие правила валидации:
CREATE DOMAIN email_type AS VARCHAR(255)
CHECK (VALUE ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$');
CREATE DOMAIN snils_type AS CHAR(14)
CHECK (VALUE ~ '^\d{3}-\d{3}-\d{3} \d{2}$');
Предикаты CHECK вычисляются для каждой вставляемой или модифицируемой строки. Могут сопоставлять несколько столбцов одного кортежа:
ALTER TABLE contract
ADD CONSTRAINT chk_contract_dates
CHECK (end_date IS NULL OR end_date >= start_date);
ALTER TABLE products
ADD CONSTRAINT chk_pricing
CHECK (discount_price IS NULL OR discount_price < base_price);
В стандарте SQL и PostgreSQL поддерживаются операторы сопоставления с регулярными выражениями:
~ — совпадение с регулярным выражением с учетом регистра;~* — совпадение без учета регистра;!~ — не совпадает (с учетом регистра);!~* — не совпадает (без учета регистра).^) и конца ($) строки. Без них выражение VALUE ~ '\d{3}' пропустит строку 'ABC123XYZ', что разрушает строгость формата!
Классический UNIQUE проверяет равенство значений: a = b. Однако реальные бизнес-процессы оперируют интервалами (время бронирования, периоды проживания, диапазоны портов, геометрические фигуры). Непересечение интервалов выражается оператором перекрытия &&, который невозможно реализовать через обычный B-Tree UNIQUE.
Для решения этой задачи PostgreSQL предоставляет ограничения исключения (EXCLUDE) на базе обобщенных поисковых деревьев (GiST):
-- 1. Подключение расширения для поддержки оператора = в GiST
CREATE EXTENSION IF NOT EXISTS btree_gist;
-- 2. Таблица бронирования конференц-залов
CREATE TABLE meeting_room_bookings (
booking_id BIGSERIAL PRIMARY KEY,
room_id INT NOT NULL,
slot TSRANGE NOT NULL,
booked_by VARCHAR(100) NOT NULL,
CONSTRAINT chk_slot_bounds CHECK (lower(slot) < upper(slot)),
CONSTRAINT exc_no_overlap EXCLUDE USING gist (
room_id WITH =,
slot WITH &&
)
);
Механизм работы: СУБД гарантирует, что для любых двух строк таблицы с одинаковым room_id их интервалы slot НЕ будут пересекаться (slot1 && slot2 вернет FALSE). Попытка параллельной вставки конфликтующей брони блокируется на уровне ядра СУБД с ошибкой exclusion_violation (23P01), устраняя проблему Race Condition без ручных блокировок LOCK TABLE.
В архитектуре баз данных нередко возникает циклическая зависимость 1:1 или 1:N:
rooms.hotel_id REFERENCES hotels(id).hotels.presidential_suite_id REFERENCES rooms(id).Если оба внешних ключа являются строгими (NOT NULL), возникает парадокс блокировки: мы не можем создать отель, так как еще не создан номер-люкс; но мы не можем создать номер-люкс, так как еще не создан отель!
ALTER TABLE hotels
ADD CONSTRAINT fk_hotel_suite
FOREIGN KEY (presidential_suite_id) REFERENCES rooms(id)
DEFERRABLE INITIALLY DEFERRED;
-- Бесконфликтная вставка в рамках одной транзакции:
BEGIN;
INSERT INTO hotels (id, name, presidential_suite_id) VALUES (1, 'Grand Palace', 101);
INSERT INTO rooms (id, hotel_id, room_number) VALUES (101, 1, 'Suite 101');
COMMIT; -- Проверка FK выполняется СТРОГО в момент COMMIT!
Режимы отложенности:
NOT DEFERRABLE (по умолчанию) — проверка выполняется немедленно после выполнения каждого DML-оператора.DEFERRABLE INITIALLY IMMEDIATE — ограничение может быть отложено, но по умолчанию проверяется после каждого оператора. Переключается в отложенный режим командой SET CONSTRAINTS fk_name DEFERRED; внутри транзакции.DEFERRABLE INITIALLY DEFERRED — проверка автоматически откладывается до фиксации транзакции (COMMIT). Если к моменту COMMIT целостность нарушена, транзакция откатывается (ROLLBACK).| Типовая ошибка | В чем проявляется дефект | Педагогический прием устранения |
|---|---|---|
| 1. Логическое противоречие предикатов | Студент задает взаимоисключающие условия: например, CHECK (age < 18 AND age >= 21) или CHECK (price > 1000 AND discount_price > price) при бизнес-требовании скидочной цены ниже базовой. В результате ни одна строка не может быть вставлена. |
Использовать метод крайних точек и диаграммы Эйлера-Венна. Попросить студента подставить конкретное тестовое число (например, age = 19) и вычислить булево значение предиката. |
| 2. Отсутствие btree_gist при EXCLUDE | Студент пишет EXCLUDE USING gist (room_id WITH =, slot WITH &&) в PostgreSQL без активации расширения и получает ошибку data type integer has no default operator class for access method gist. |
Объяснить, что GiST «из коробки» умеет индексировать только геометрические и интервальные типы. Для поддержки скалярных равенств (=) требуется расширение CREATE EXTENSION btree_gist. |
| 3. Забытые якоря в RegEx | Студент пишет CHECK (passport ~ '\d{4}'). В базу успешно записываются строки вида 'серия 12345678', так как подстрока найдена. |
Продемонстрировать на проекторе разницу между поиском подстроки и строгим сопоставлением всего значения: всегда требовать ^ в начале и $ в конце шаблона. |
| 4. Непонимание работы с NULL в CHECK | Студент полагает, что если поле равно NULL, условие CHECK (discount < 50) вернет FALSE и отклонит строку. |
Разобрать трехзначную логику SQL (TRUE, FALSE, UNKNOWN). Напомнить: ограничение CHECK отклоняет строку только тогда, когда предикат вычисляется в FALSE. Если предикат вычисляется в UNKNOWN (из-за NULL), строка принимается! Для запрета NULL обязателен NOT NULL. |
| 5. Попытка повесить DEFERRABLE на CHECK | Студент объявляет CHECK (...) DEFERRABLE в стандартной СУБД. |
Пояснить стандарт SQL: предикаты CHECK проверяются построчно в момент выполнения DML. Откладывать до конца транзакции допускается только внешние ключи (FOREIGN KEY) и уникальные ограничения (UNIQUE / PRIMARY KEY). |
exclusion_violation создает сильнейший эмоциональный якорь.| Баллы | Критерий | Дескриптор выполнения |
|---|---|---|
| 2 балла | Спецификация ограничений CHECK / EXCLUDE | Синтаксически безупречный DDL. Корректные регулярные выражения с якорями ^...$. Безошибочное конструирование интервального ограничения EXCLUDE USING gist с типами данных и операторами. Учтена трехзначная логика SQL и значения NULL. |
| 1 балл | Отложенные ограничения DEFERRABLE | Корректно спроектирован циклический внешний ключ. Обоснован выбор режима INITIALLY DEFERRED или INITIALLY IMMEDIATE. Понята механика вставки взаимосвязанных сущностей внутри транзакции BEGIN...COMMIT. |
| 1 балл | Выявление конфликтов и самопроверка | Студент нашел потенциальные логические противоречия в спецификации, обосновал невозможность использования наивных проверок и ответил на все контрольные вопросы самопроверки. |