17

I have a table in which I store all the forum messages posted by the users on my website. The messages hierarchy strucrue is implement using a Nested set model.

The following is a simplified structure of the table:

  • Id (PRIMARY KEY)
  • Owner_Id (FOREIGN KEY REFERENCES TO Id)
  • Parent_Id (FOREIGN KEY REFERENCES TO Id)
  • nleft
  • nright
  • nlevel

Now, the table is looking something like this:

+ ------- + ------------- + -------------- + ---------- + ----------- + ----------- +
| Id      | Owner_Id      | Parent_Id      | nleft      | nright      | nlevel      |
+ ------- + ------------- + -------------- + ---------- + ----------- + ----------- +
| 1       | 1             | NULL           | 1          | 8           | 1           |
| 2       | 1             | 1              | 2          | 5           | 2           |
| 3       | 1             | 2              | 3          | 4           | 3           |
| 4       | 1             | 1              | 6          | 7           | 2           |
+ ------- + ------------- + -------------- + ---------- + ----------- + ----------- +

Note that the first row is the root message, and the tree of this post can be displayed as:

-- SELECT * FROM forumTbl WHERE Owner_Id = 1 ORDER BY nleft;

MESSAGE (Id = 1)
    MESSAGE (Id = 2)
        Message (Id = 3)
    Message (Id = 4)

My problem occurs when I try to delete all the rows under the same Owner_Id in a single query. Example:

DELETE FROM forumTbl WHERE Owner_Id = 1 ORDER BY nright;

The above query fails with the following error:

Error Code: 1451. Cannot delete or update a parent row: a foreign key constraint fails (forumTbl, CONSTRAINT Owner_Id_frgn FOREIGN KEY (Owner_Id) REFERENCES forumTbl (Id) ON DELETE NO ACTION ON UPDATE NO ACTION)

The reason is that the first row, which is the root node (Id=1), also has the same value in its Owner_Id field (Owner_Id=1), and it causing the query to fail due to the foreign key constraint.

My question is: How can I prevent this foreign key constraint circularity and delete a row that reference to itself? Is there a way to do it without first having to update the the Owner_Id of the root row to NULL?

I created a demo of this scenario: http://sqlfiddle.com/#!9/fd1b1

Thank you.

0

3 Answers 3

12
  1. Besides disabling foreign keys which is is dangerous and can lead to inconsistencies, there are two other options to consider:

  2. Modify the FOREIGN KEY constraints with the ON DELETE CASCADE option. I haven't tested all cases but you surely need this for the (owner_id) foreign key and possibly for the other as well.

    ALTER TABLE forum
        DROP FOREIGN KEY owner_id_frgn,
        DROP FOREIGN KEY parent_id_frgn ;
    ALTER TABLE forum
        ADD CONSTRAINT owner_id_frgn
            FOREIGN KEY (owner_id) 
            REFERENCES forum (id)
            ON DELETE CASCADE,
        ADD CONSTRAINT parent_id_frgn
            FOREIGN KEY (parent_id) 
            REFERENCES forum (id)
            ON DELETE CASCADE ;
    

    If you do this, then deleting a node and all the descendants from the tree is simpler. You delete a node and all descendants are deleted through the cascade actions:

    DELETE FROM forum
    WHERE id = 1 ;         -- deletes id=1 and all descendants
    
  3. The issue that you stepped into is actually 2 issues. The first is that deleting from a table with self-referencing foreign key is not a serious problem for MySQL, as long as there is no row that references itself. If there is a row, as in your example, the options are limited. Either disable foreign keys or use CASCADE action. But if there are no such rows, deleting becomes a smaller problem.

    So, if we decide to store NULL instead of the same id in owner_id, then you could delete without disabling foreign keys and without cascades.

    You would then stumble upon the second problem! Running your query would raise a similar error:

    DELETE FROM forum 
    WHERE owner_id = 1 OR id = 1 ; 
    

    Error(s), warning(s):
    Cannot delete or update a parent row: a foreign key constraint fails (rextester.forum, CONSTRAINT owner_id_frgn FOREIGN KEY (owner_Id) REFERENCES forum (id))

    The reason for this error would be different than before though. It's because MySQL checks each constraint after every row is deleted and not (as it should) at th eend of the statement. So when a parent is deleted before its child is deleted, we get a foreign key constraint error.

    Fortunately, there is a simple solution for this, thnx to the nested set model and to that MySQL allows us to set an order for the deletes. We just have to order by nleft DESC or by nright DESC, which makes sure that all the children are deleted before a parent:

    DELETE FROM forum 
    WHERE owner_id = 1 OR id = 1 
    ORDER BY nleft DESC ; 
    

    Minor note, we could (or should) use a condition that considers the nested model as well. This is equivalent (and might use an index on (nleft, nright) to find which nodes to delete:

    DELETE FROM forum 
    WHERE nleft >= 1 AND nright <= 8 
    ORDER BY nleft DESC ; 
    
0
9
SET FOREIGN_KEY_CHECKS=0;
DELETE FROM forum WHERE Owner_Id = 1 ORDER BY nright;
SET FOREIGN_KEY_CHECKS=1;

just not forget in this case You must manually parse situations when parent_id show to 1, because You not use cascade

0
1

I hope this answers would help others.

Show Table Constraints

If you don't know table constraints, this will help.

USE INFORMATION_SCHEMA;
SELECT TABLE_NAME,
       COLUMN_NAME,
       CONSTRAINT_NAME,
       REFERENCED_TABLE_NAME,
       REFERENCED_COLUMN_NAME
FROM KEY_COLUMN_USAGE
WHERE TABLE_SCHEMA = "database_name" 
      AND TABLE_NAME = "table_name" 
      AND REFERENCED_COLUMN_NAME IS NOT NULL;

In order to delete data from multiple table in database, It is required to drop constraints first and then ADD CONSTRAINT.

DROP FOREIGN KEY constraints_name

Constraint name can be simplify:

  1. table_name: addresses
  2. foreign key: user_id
  3. add keyword _foreign in end

Example:

ALTER TABLE addresses
    DROP FOREIGN KEY addresses_user_id_foreign;
ALTER TABLE addresses
    ADD CONSTRAINT addresses_user_id_foreign
        FOREIGN KEY (user_id) 
        REFERENCES users (id)
        ON DELETE CASCADE;

 

Ref1: https://dev.mysql.com/doc/refman/8.0/en/create-table-foreign-keys.html Ref2: https://www.mysqltutorial.org/mysql-foreign-key/

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.