多列外键:将单列设置为 Null "ON DELETE" 而不是全部
Multiple Column Foreign Key: Set single column to Null "ON DELETE" instead of all
一般: 给定多个列的外键,其中一些可能为 NULL。
默认情况下 (MATCH SIMPLE) MySQL/MariaDB 只要多列外键的至少一列为 NULL,InnoDB 就不会检查外键。
要求:如果从父项中删除了一行,则相应子项的一列应设置为NULL,但外键中的两列都不应设置为NULL。
Example/Description:一个学生可能会被列为一个讲座,也可能被列为一个讲座组。如果删除讲座,则应删除所有学生列表 (Works) 及其所有组 (Works)。如果只删除一个组,那么学生应该仍然被列为讲座,但他们不应该再被分配到一个组(问题)。
Example/SQL:下面的SQL说明了这个例子,但是最后一条语句是行不通的,因为最后一个FOREIGN KEY同时需要lectureId和groupId可以为 NULL,但是使两者都为 NULL 将意味着删除组也会将 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
);
经过一些研究,似乎无法使用外键实现特定要求。
最好的解决方案似乎是混合使用外键和触发器。
可以通过以下语句解决给定示例的问题:
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 SET NULL" 与相同的触发器一起使用,而不是使用 "ON DELETE CASCADE",但是 "lectureId" 必须可以为 null,并且应该包含一个 "CHECK (lectureId IS NOT NULL)" 以确保它永远不会设置为 null
一般: 给定多个列的外键,其中一些可能为 NULL。
默认情况下 (MATCH SIMPLE) MySQL/MariaDB 只要多列外键的至少一列为 NULL,InnoDB 就不会检查外键。
要求:如果从父项中删除了一行,则相应子项的一列应设置为NULL,但外键中的两列都不应设置为NULL。
Example/Description:一个学生可能会被列为一个讲座,也可能被列为一个讲座组。如果删除讲座,则应删除所有学生列表 (Works) 及其所有组 (Works)。如果只删除一个组,那么学生应该仍然被列为讲座,但他们不应该再被分配到一个组(问题)。
Example/SQL:下面的SQL说明了这个例子,但是最后一条语句是行不通的,因为最后一个FOREIGN KEY同时需要lectureId和groupId可以为 NULL,但是使两者都为 NULL 将意味着删除组也会将 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
);
经过一些研究,似乎无法使用外键实现特定要求。
最好的解决方案似乎是混合使用外键和触发器。
可以通过以下语句解决给定示例的问题:
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 SET NULL" 与相同的触发器一起使用,而不是使用 "ON DELETE CASCADE",但是 "lectureId" 必须可以为 null,并且应该包含一个 "CHECK (lectureId IS NOT NULL)" 以确保它永远不会设置为 null