Note: this question is more about database design and SQL Server than specific ORMDB like EF Core.
I have a database schema that looks like this:
Using EF Core Migration, all the SQL statements can be run until the very last constraint (FK_BookReleases_Nicknames_NicknameId). The previous FK FK_BookReleases_Books_BookId could be added.
The error I receive is (as many other articles on SO):
Introducing FOREIGN KEY constraint
'FK_BookReleases_Nicknames_NicknameId' on table 'BookReleases' may
cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or
ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
My question is, where is the flaw on that design? I cannot understand under any logic such situation happens. And what is the proper fix? I see many suggested changing ON DELETE action but not specific on which FK and what other action I should use.
Worse, even already read this article I still don't understand how it could be a problem, and how would his proposed solution fits in the above schema.
The issue is due to multiple cascading paths from Author (Grand Parent) to BookRelease (Grand Child). There are two cascading paths:
Author -> Book -> BookRelease
Author -> Nickname -> BookRelease
This is discussed in detail in the post in MSSQLTips
So, the way to handle this is:
Disable the ON DELETE CASCAE and choose NOACTION as the foreign key creation.
Create INSTEAD OF DELETE TRIGGERS in Author(GrandParent), Book(Child1), Nickname(Child2) tables to handle the deletion of parent keys in the child tables.
Grand Parent deletion : Delete in GrandChild, followed by Child1,
followed by Child2,
Child1 deletion: Delete in GrandChild, followed
by Child1
Child2 deletion: Delete in GrandChild, followed by Child2
Related
I have a fairly simple design, as follows:
What I want to achieve in my grouping_individual_history is marked in red:
when a session is deleted, I want to cascade delete the grouping_history....
when a grouping is deleted, I just want the child field to be nullified
It seems that MSSQL will not allow me to have more than one FK that does something else than no action ... It'll complain with:
Introducing FOREIGN KEY constraint 'FK_grouping_individual_history_grouping' on table 'grouping_individual_history' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
I've already read this post (https://www.mssqltips.com/sqlservertip/2733/solving-the-sql-server-multiple-cascade-path-issue-with-a-trigger/), although it's not quite the same scenario it seems to me.
I've tried doing a INSTEAD OF DELETE trigger on my grouping table, but it wont accept it, because in turn, my grouping table has another FK (fkSessionID) that does a cascade delete... So, the fix would be to change it all, in all affected tables with FKs. The chain is long though, and we cannot consider it.
For one thing, can someone explain to me why SQL Server is giving me the issue for this very simple scenario in the first place? I just don't understand it.
Is there another workaround I could use (besides just removing the foreign key link from my grouping_individual_history table)?
I've learned that SQLAlchemy implements some foreign key handling such as setting them null when the parent is deleted, separately from the database, meaning they can be set to different behaviors, and different ways of doing the same thing could get either one. Example:
I have Comment and Subscription, where Subscription has a foreign key relationship to Comment. I started by setting backref = backref('subs', cascade = 'delete, delete-orphan') on the Subscription's relationship, and the result was that session.delete(comment) would delete the subscriptions on the comment properly, but session.query(Comment).filter_by(id = id).delete() would fail with a foreign key violation, because the cascade was set at the ORM level instead of the Postgres level.
Needless to say I find this very confusing, and I want to disable it so that all foreign key handling on deletes is done by Postgres. I'm not finding an obvious way to do it looking at the docs. I read about passive deletes, which sounds like it does what I want except that it doesn't apply to objects already loaded into the session.
Is there a way to disable all ORM-level foreign key handling on deletes? And is there a good reason not to?
Huh, I tried out passive deletes and it seems like it does what I need after all. The words "The cascade="all, delete-orphan" will take effect for instances of MyOtherClass which are currently present in the session" made me think that it would still manually issue deletes for dependent objects if they were added to the session, but if I fetch the depended object and then the dependent one and then modify the dependent, verifying that it's in session.dirty, and then delete the depended and commit, SQLAlchemy doesn't manually issue a delete for the dependent object.
I have a simple datamodel in my database with three tables:
State
Ticket
SubTicket
One ticket can have zero to multiple subtickets.
Every ticket and every tubticket can have one state.
So my database looks like this:
between: xTicket and xState i have the constraint:
on update cascade / on delete no action (updating the StateID in xTicket when changed, probihit deleting the entry in xTicket)
between: xSubTicket and xTicket I have the constraint:
on update cascade / on delete cascade (updating the TicketID in xSubTicket and deleting an entry when an entry in xTicket is deleted)
but when I want to the same constraint as for xTicket and xState for:
on update cascade / on delete no action I get the following message:
Foreign key constraint may cause cycles or multiple cascade paths
the foreign key will only let me set the constraint ON UPDATE CASCADE to either between xTicket and xState or between xSubTicket and xState.
so far I have found other questions about the same issue, where I got to know: either change the database design properly or the use of INSTEAD OF Triggers. - I actually want to know why this design is not acceptable, what am I doing wrong? How do I do it correctly?
thanks for any suggestion in advance
I actually want to know why this design is not acceptable,
Why do you think ti is not acceptable? This is merely a limitation of SQL Server. An inconvenient one.
You are free to code around it and use triggers to do the cascading operations.
I would ague your design is broken because hard deleting states should never happen and you try to do the database do cleanup that may well be more complex. I would go for a soft delete (marking a state as not available for new objects) so the cascade operations make no sense there. But that is another discussion and outside of the topic of your question.
Ok. I've tried the trigger, but it didn't work.
I have Cascades from A to Linker and from B to Linker, Cascade from Users to A, No Action from Users to B.
My trigger is on Users and is as follows:
set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
go
ALTER TRIGGER [trig_delUser] ON [dbo].[aspnet_Users]
FOR DELETE AS
SET NOCOUNT ON;
DELETE FROM B WHERE B.UserId = Deleted.UserId
I get the exception: The DELETE statement conflicted with the REFERENCE constraint "FK_B_aspnet_Users"
I am working with a modified aspnetdb
SQL database:
[Partial DB diagram][2]
I have cascade deletion on the
B_Linker relationship and the A_Linker
relationship and there doesn't seem to
be any danger of cycles occuring.
When I delete a user, I would like all
A entries and B entries to be deleted
along with any associated linker
entries; unfortunately, SQL mgmt
studio will only let me put a cascade
delete rule on EITHER aspnet_Users_A
or aspnet_Users_B, not both.
What do I need to do?
Many Thanks.
[2]:
http://i48.tinypic.com/2nsnc3k.png
This is one of the unfortunate and frankly rather annoying limitations of SQL Server.
It's not the fact that there could be cycles that's the problem, it's simply as the error says - you have multiple cascade paths to the Linker table. The first is aspnet_Users -> A -> Linker and the second is aspnet_Users -> B -> Linker.
You only really have a couple of choices:
Choose one path to implement the CASCADE on and set the other to NO ACTION. Then write a Stored Procedure that deletes the non-cascaded child entities before deleting the parent entities in order to prevent a foreign key error. Or, don't CASCADE either and have your SP do the cascading for both.
Don't add a foreign key at all on the second relationship; instead, use a FOR DELETE trigger on the parent to delete the child entities. I very much dislike using triggers for RI, but this isn't much worse than the first option. In some ways it's better, because database clients don't have to worry about your specific implementation of the FK relationships.
Neither are ideal, but there is no perfect solution other than to change your design. If it is possible for you to change your schema such that you don't have multiple cascade paths, that would be the best thing to do - but I recognize that there are (many) real-world situations where this is not possible. Not knowing the specifics of your schema, I can't say for sure whether or not there's a more optimal design.
I would not implement this through on delete cascade. Or through a trigger. What would happen if someone needed to delete 40,0000 records all at once. You would lock up the parent and all the child tables possibly for minutes possibly hours while it runs through and does it's thing.
So really what you should do is write the deletes to the child tables first then the delete to the parent table and put them in a transaction. This will work well enough when you are delting one record at a time. Large deletes should be written separately (don't loop throuhg the existing proc) and are another subject, but at least now you can do them without killing your server.
You just have to put the ON DELETE CASCADE rules on the foreign keys.
I have the following table relationship in my database:
Parent
/ \
Child1 Child2
\ /
GrandChild
I am trying to create the FK relationships so that the deletion of the Parent table cascades to both child and the grandchild table. For any one particular granchild, it will either be parented to one or the other child tables, but never both at the same time.
When I'm trying to add ON DELETE CASCADE to the FK relationships, everything is fine adding them to one "side" of the two children (Parent-Child1-GrandChild is fine for Cascade Delete). However, as soon as I add the Cascade Delete on the Child2 "side" of the relationship SQL tells me that the FK would cause multiple cascade paths. I was under the impression that multiple cascade paths only apply when more than one FK indicates the SAME table. Why would I be getting the multiple cascade paths error in this case?
PS The table relationships at this point would be very difficult to change so simply telling me to change my table structure is not going to be helpful, thanks.
The message means that if you delete a Parent record, there are two paths that lead to all deletable GrandChild records.
Fix: Remove the ON DELETE CASCADE options in the FKs, and create INSTEAD OF DELETE triggers for the ChildX tables, deleting all grandchild records, and then the childX records themselves.