0

My problem is very similar to this: Multiple Column Foreign Key: Set single column to Null "ON DELETE" instead of all

I will be using that problem and its code. The code in the answer is exactly the solution I am looking for, but there is an error.

A student might be listed for a lecture, and optionally for one of the lectures groups as well. If the lecture is deleted all student listing should be removed and all its groups. If a group is deleted, then the students should still be listed for the lecture, but they should not be assigned to a group anymore.

All of the above works. However, if I try to change the lecture id, I would expect the reference in studentListed and groups would be updated accordingly (On Update Cascade), but instead, it fails on this foreign key constraint for some reason:

#1452 - Cannot add or update a child row: a foreign key constraint fails
(`SomeDB`.`studentListed`, CONSTRAINT `studentListed_ibfk_1` FOREIGN KEY (`lectureId`)
REFERENCES `lectures` (`lectureId`) ON DELETE CASCADE ON UPDATE CASCADE)

Why does this happen? The cause seems to be that lectureId in studentListed is both a reference to lectureId in lectures and a reference to lectureId in groups which is itself a reference. Is there a better way to do this?

Sql code to reproduce:

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;

INSERT INTO lectures
VALUES
(1, "lecture1");

INSERT INTO groups
VALUES
(1, 1, "group1");

INSERT INTO studentListed
VALUES
(1, 1, 1);

UPDATE lectures SET lectureId=2 WHERE lectureId=1; /* Offending line */
John
  • 41
  • 1
  • 7
  • Please put everything needed to ask this question in this post. Give just what you need & relate it to your problem. – philipxy Oct 15 '21 at 22:22

1 Answers1

1

you can't change to lectureids 2 because none exists in the first place in the table

And the cascade works the other way

Further GROUPS os a reserved word in mysql, and should so be avoided.

Last i had to remove the second foreign key, as it already exists in groups and is so unnecessary, as the fpoeign key in groups in groups already chekcs if such a lectureid exists

EXAMPLE for the working of foreign keys

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;
INSERT INTO lectures
VALUES
(1, "lecture1");
INSERT INTO `groups`
VALUES
(1, 1, "group1");
INSERT INTO studentListed
VALUES
(1, 1, 1);
UPDATE lectures SET lectureId = 2 WHERE lectureId = 1
SELECT * FROM studentListed
studentId | lectureId | groupNo
--------: | --------: | ------:
        1 |         2 |       1
UPDATE lectures SET lectureId=2 WHERE lectureId=1; /* Offending line */
✓

✓

db<>fiddle here

yu can't build that in t your way you have to break up the primary key from Groups and reference only groups with it, like

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 PRIMARY KEY,
  title VARCHAR(10) NOT NULL,
  UNIQUE 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 (groupNo) REFERENCES `groups` (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;
INSERT INTO lectures
VALUES
(1, "lecture1");
INSERT INTO `groups`
VALUES
(1, 1, "group1");
INSERT INTO studentListed
VALUES
(1, 1, 1);
UPDATE lectures SET lectureId = 2 WHERE lectureId = 1
SELECT * FROM studentListed
studentId | lectureId | groupNo
--------: | --------: | ------:
        1 |         2 |       1
UPDATE lectures SET lectureId=2 WHERE lectureId=1; /* Offending line */
✓

✓
DELETE FROM lectures WHERE lectureID = 2
SELECT * FROM studentListed
studentId | lectureId | groupNo
--------: | --------: | ------:

db<>fiddle here

this will need still more improvment as your concept egts more complicated

nbk
  • 45,398
  • 8
  • 30
  • 47
  • The problem with deleting the first foreign key is studentListed lectureId won't cascade with lecture unless the student is in a group. For example if you delete a lecture then it will not delete the corresponding studentListed entries. – John Oct 17 '21 at 16:42
  • you can't put both foreign keys in that table. every change will violate on foreign key or the other. – nbk Oct 17 '21 at 16:53
  • @John also your logic is flawed because irt will delete frist the group and then the studentListed see https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=bde6ee1446c72fe5a8a1356cf26c02ee the problem with the groups can be soved your way if you want to include groups then it can't be in the foreign key relation ship – nbk Oct 17 '21 at 17:00
  • Try without inserting any groups and delete a lecture a student is on. You can also insert students for lectures that don't exist. https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=418d818333a16be60ff13ed7e91a9d88 – John Oct 17 '21 at 17:48
  • please read my coments again carefully and see the secound example how you must solve this, the combination with the combined primary key for groups doesn't work at all and you will also never get it to work – nbk Oct 17 '21 at 17:57
  • Sorry, I missed your edit. However, a student on team 1 can now join a group on team 3 for example. I suppose I could use triggers on studentListed to ensure the group is of the same team. https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=71e7b4877ed68fc4cc8fe0677406deb5 – John Oct 17 '21 at 19:10
  • yes trigger can solve it – nbk Oct 17 '21 at 19:25