Внешний ключ должен обнуляться при удалении родителя, но его столбец объявлен обязательным. Какое противоречие возникает?
ON DELETE SET NULL несовместим с NOT NULL на столбце внешнего ключа. При удалении родительской строки СУБД должна записать NULL в дочернюю, но ограничение NOT NULL это запрещает, поэтому операция удаления будет отклонена или действие ссылочной политики завершится ошибкой — точное проявление зависит от СУБД.
Внешние ключи появились как механизм поддержания ссылочной целостности на уровне базы данных, а не только в прикладном коде. Реляционная модель допускает, что связь с родительской сущностью может быть обязательной или необязательной, поэтому схема должна явно выражать это различие.
Ссылочные действия вроде SET NULL, CASCADE и RESTRICT позволяют заранее определить судьбу зависимых строк при изменении родителя. Их смысл сохраняется только тогда, когда целевое действие совместимо с другими ограничениями столбцов.
Представим таблицу заказов и необязательную ссылку на менеджера. Если менеджер удалён, заказ можно сохранить, установив у него отсутствие менеджера. Для этого столбец ссылки должен принимать NULL.
Если тот же столбец объявлен обязательным, схема одновременно требует двух несовместимых состояний: значение должно отсутствовать после удаления родителя и при этом не может быть отсутствующим. Это не просто неудобство приложения: конфликт обнаруживается самой базой данных при выполнении операции.
Минимальная совместимая схема выглядит так:
Столбец reviewer_id допускает NULL, потому что после удаления сотрудника база должна заменить ссылку на NULL. Пока сотрудник существует, указанное ненулевое значение всё равно обязано ссылаться на строку родительской таблицы.
Если связь обязательна, SET NULL не подходит. Возможные варианты — запретить удаление родителя через RESTRICT или NO ACTION, переназначить зависимые строки на другого родителя до удаления, либо удалить их через CASCADE, если потеря этих строк допустима.
Для составного внешнего ключа правило применяется ко всем его столбцам: при SET NULL они должны позволять NULL. Кроме того, нужно учитывать бизнес-смысл: разрешение NULL меняет модель с обязательной связи на необязательную, поэтому нельзя снимать NOT NULL только ради устранения ошибки, не проверив требования предметной области.
В системе задач удаление сотрудника сначала настроили с ON DELETE SET NULL, но поле исполнителя оставили обязательным. В результате удаление сотрудника стало отклоняться, хотя ожидалось сохранение задач без исполнителя.
Рассматривались три варианта. CASCADE был опасен: вместе с сотрудником могли исчезнуть исторические задачи. RESTRICT сохранял целостность, но требовал сначала вручную переназначить все задачи. SET NULL с допускающим NULL полем сохранял историю и корректно отражал отсутствие текущего исполнителя.
Выбрали третий вариант, а для активных задач добавили отдельное прикладное правило: перед закрытием или удалением сотрудника их нужно переназначить. Так исторические данные не теряются, а обязательность исполнителя контролируется там, где она действительно нужна.
Вопрос: Можно ли использовать SET NULL, если внешний ключ состоит из нескольких столбцов и только один из них допускает NULL?
Ответ: Обычно нет. Действие должно установить NULL во все компоненты внешнего ключа, поэтому каждый из них должен быть nullable. Частично обнулённая составная ссылка часто либо запрещается ограничениями столбцов, либо не соответствует правилам сопоставления составного ключа.
Вопрос: Чем NO ACTION отличается от RESTRICT в контексте удаления родителя?
Ответ: Оба варианта не позволяют оставить нарушенную ссылку, но момент проверки может различаться. RESTRICT обычно запрещает операцию сразу, тогда как NO ACTION может проверять нарушение в конце оператора или транзакции, особенно в СУБД, поддерживающих отложенные ограничения. Точное поведение зависит от конкретной СУБД и режима deferrable-ограничений.
Вопрос: Почему замена SET NULL на специальное значение вроде нуля не является эквивалентным решением?
Ответ: Ноль становится обычным значением внешнего ключа и должен ссылаться на существующую родительскую строку, если ограничение внешнего ключа действительно. Если такой строки нет, целостность будет нарушена; если её создать искусственно, появится фиктивная сущность, смешивающая реального родителя с состоянием «родитель отсутствует». NULL явно выражает отсутствие связи и позволяет отличить его от любого допустимого идентификатора.