ahoo
ahoo

Reputation: 1361

MySQL ON UPDATE CASCADE not CASCADEing

Suppose i have two below table:

CREATE TABLE post (
  id bigint(20)     NOT NULL    AUTO_INCREMENT,
  text text ,

  PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=1;

CREATE TABLE post_path (
  ancestorid bigint(20)     NOT NULL    DEFAULT '0',
  descendantid bigint(20)   NOT NULL    DEFAULT '0',
  length int(11)            NOT NULL    DEFAULT '0',

  PRIMARY KEY (ancestorid,descendantid),
  KEY descendantid (descendantid),

  CONSTRAINT f_post_path_ibfk_1 
    FOREIGN KEY (ancestorid) REFERENCES post (id) 
    ON DELETE CASCADE 
    ON UPDATE CASCADE,
  CONSTRAINT f_post_path_ibfk_2 
    FOREIGN KEY (descendantid) REFERENCES post (id) 
    ON DELETE CASCADE 
    ON UPDATE CASCADE
) ENGINE=InnoDB;

And inserted these rows:

INSERT INTO 
    post (text)
    VALUES ('a'); #// inserted row by id=1
INSERT INTO 
    post_path (ancestorid ,descendantid ,length) 
    VALUES (1, 1, 0);

When i want to update post row id:

 UPDATE post SET id = '10' WHERE post.id =1

MySQL said:

#1452 - Cannot add or update a child row: a foreign key constraint fails (test.post_path, CONSTRAINT f_post_path_ibfk_2 FOREIGN KEY (descendantid) REFERENCES post (id) ON DELETE CASCADE ON UPDATE CASCADE) 

Why? what is wrong?

Edit:

When i inserted these rows:

INSERT INTO 
    post (text)
    VALUES ('b'); #// inserted row by id=2

INSERT INTO 
    post_path (ancestorid, descendantid, length)
    VALUES (1, 2, 0);

And updated:

UPDATE post SET id = '20' WHERE post.id =2

Mysql updated successfully both child and parent row. so Why i can not update first post (id=1)?

Upvotes: 13

Views: 7690

Answers (4)

blackmambo
blackmambo

Reputation: 152

I believe the solution to your problem is to remove descendantid as a constraint and use a trigger to perform an update on the field.

delimiter $$
CREATE TRIGGER post_trigger
AFTER UPDATE ON post
FOR EACH ROW
BEGIN
UPDATE post_path SET post_path.descendantid = NEW.id WHERE post_path.descendantid = OLD.id
END$$

Upvotes: 1

Brooksie
Brooksie

Reputation: 31

The reason the first update fails and second does not is because in the second instance your ancestorid and descendantid reference different rows in your post table,

ancestorid   = 1
descendantid = 2

The first update fails when it attempts to update post_path.ancestorid as in doing so the constraint between post.id and post_path.descendantid fails as these values would no longer match (1 !== 10).

Assuming that any given post cannot be both an ancestor and a descendant then the issue here is only in the execution of the first insert:

INSERT INTO `post_path` (`ancestorid` ,`descendantid` ,`length`) VALUES (1, 1, 0);

Upvotes: 0

ayan sil
ayan sil

Reputation: 91

The main reason why the second one worked is that you have kept different values for ancestorid and descendantid. When you are making two different constraints on the basis of a change on a particular attributes. only the first constraint will work, not the second one. Which is the case in your first update try.

Upvotes: 0

Mike Purcell
Mike Purcell

Reputation: 19989

Ok, I ran your schema and queries through a test database I have access too and noticed the following; after inserting both rows to both tables, and before any updates the data looks like:

mysql> select * from post;
+----+------+
| id | text |
+----+------+
|  1 | a    |
|  2 | b    |
+----+------+
2 rows in set (0.00 sec)

mysql> select * from post_path;
+------------+--------------+--------+
| ancestorid | descendantid | length |
+------------+--------------+--------+
|          1 |            1 |      0 |
|          1 |            2 |      0 |
+------------+--------------+--------+
2 rows in set (0.00 sec)

After I issue the update statement, to update post.id to 20:

mysql> UPDATE `post` SET `id` = '20' WHERE `post`.`id` =2;
Query OK, 1 row affected (0.08 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select * from post_path;
+------------+--------------+--------+
| ancestorid | descendantid | length |
+------------+--------------+--------+
|          1 |            1 |      0 |
|          1 |           20 |      0 |
+------------+--------------+--------+
2 rows in set (0.00 sec)

Notice that the ancestorid is still 1, this appears to be an issue with MySQL:

If you use a multiple-table UPDATE statement involving InnoDB tables for which there are foreign key constraints, the MySQL optimizer might process tables in an order that differs from that of their parent/child relationship. In this case, the statement fails and rolls back. Instead, update a single table and rely on the ON UPDATE capabilities that InnoDB provides to cause the other tables to be modified accordingly. See Section 14.3.5.4, “InnoDB and FOREIGN KEY Constraints”.

The reason why your first query is failing, is because ancestorid is not updating to 10, but descendantid is, and because you are trying to set post.id to 10, and ancestorid in post_path table is still referencing the value 1, which would no longer exist.

You should consider altering your schema to avoid this, and to also avoid updating an auto_increment column so you avoid collisions.

Upvotes: 6

Related Questions