Задний ключ с несколькими столбцами: установите одиночный столбец в значение NULL "ON DELETE" вместо всех

Общие. Для внешнего ключа в нескольких столбцах некоторые из них могут быть NULL.
По умолчанию (MATCH SIMPLE) MySQL/MariaDB InnoDB не проверяет внешний ключ, если по крайней мере один столбец внешнего ключа с несколькими столбцами равен NULL.

Требование. Если строка удалена из родительского столбца соответствующего дочернего элемента, необходимо установить значение NULL, но не оба столбца внешнего ключа.

Пример/Описание. Студент может быть указан для лекции и, возможно, для одной из групп лекций. Если лекция удалена, все листы учащихся должны быть удалены (Работы) и все его группы (Работы). Если удалена только одна группа, ученики все равно должны быть указаны для лекции, но они больше не должны назначаться группе (проблема).

Пример /SQL . Следующий пример иллюстрирует этот пример, но последнее утверждение не будет работать, так как последний FOREIGN KEY требует, чтобы как lectureId, так и groupId были NULLable, но при этом оба NULLable будут означать, что удаление группы также установит для lectureId значение NULL.

CREATE TABLE lectures (
  lectureId INT NOT NULL,
  title VARCHAR(10) NOT NULL,
  PRIMARY KEY (lectureId)
 );

CREATE TABLE groups (
  lectureId INT NOT NULL,
  groupNo INT NOT NULL,
  title VARCHAR(10) NOT NULL,
  PRIMARY KEY (lectureId,groupNo),
  FOREIGN KEY (lectureId) REFERENCES lectures (lectureId)
    ON UPDATE CASCADE ON DELETE CASCADE
 );

CREATE TABLE studentListed (
  studentId INT NOT NULL,
  lectureId INT NOT NULL,
  groupNo INT NULL,
  PRIMARY KEY (studentId,lectureId),
  FOREIGN KEY (lectureId) REFERENCES lectures (lectureId)
    ON UPDATE CASCADE ON DELETE CASCADE,
  FOREIGN KEY (lectureId,groupNo) REFERENCES groups (lectureId,groupNo)
    ON UPDATE CASCADE ON DELETE SET NULL
 );

Ответ 1

После некоторых исследований кажется, что это особое требование не реализуется с использованием внешних ключей.

Лучшее решение, похоже, использует сочетание Внешних ключей и Триггер.

Проблема может быть решена для данного примера следующими утверждениями:

CREATE TABLE lectures (
  lectureId INT NOT NULL,
  title VARCHAR(10) NOT NULL,
  PRIMARY KEY (lectureId)
 );

CREATE TABLE groups (
  lectureId INT NOT NULL,
  groupNo INT NOT NULL,
  title VARCHAR(10) NOT NULL,
  PRIMARY KEY (lectureId,groupNo),
  FOREIGN KEY (lectureId) REFERENCES lectures (lectureId)
    ON UPDATE CASCADE ON DELETE CASCADE
 );

CREATE TABLE studentListed (
  studentId INT NOT NULL,
  lectureId INT NOT NULL,
  groupNo INT NULL,
  PRIMARY KEY (studentId,lectureId),
  FOREIGN KEY (lectureId) REFERENCES lectures (lectureId) 
    ON UPDATE CASCADE ON DELETE CASCADE,
  FOREIGN KEY (lectureId,groupNo) REFERENCES groups (lectureId,groupNo)
    ON UPDATE CASCADE ON DELETE CASCADE
 );

CREATE TRIGGER GroupDelete BEFORE DELETE ON groups
FOR EACH ROW
  UPDATE studentListed SET studentListed.groupNo = NULL
    WHERE studentListed.lectureId = OLD.lectureId
    AND studentListed.groupNo = OLD.groupNo;

Обратите внимание, что "ON DELETE CASCADE" последнего внешнего ключа никогда не приведет к каскадному удалению, поскольку триггер уже удалил ссылки на внешние ключи, опуская соответствующие строки.

Дополнение: вместо использования "ON DELETE CASCADE" можно использовать "ON DELETE SET NULL" с тем же триггером, но тогда "lectureId" должен быть нулевым, и нужно включить "CHECK (lectureId IS NOT NULL)", чтобы он никогда не был установлен в значение null