SQLite User Forum

Introducing FOREIGN KEY constraint may cause cycles or multiple cascade paths
Login

Introducing FOREIGN KEY constraint may cause cycles or multiple cascade paths

(1) By Mark Benningfield (mbenningfield1) on 2022-11-04 22:09:14 [source]

With this simple schema in SQLServer:

CREATE TABLE [dbo].[titles] (
    [titleid] INT           NOT NULL,
    [title]   NVARCHAR (50) NULL,
    PRIMARY KEY CLUSTERED ([titleid] ASC)
);

GO

CREATE TABLE [dbo].[divs] (
    [divid]   INT           NOT NULL,
    [titleid] INT           NOT NULL,
    [name]    NVARCHAR (50) NULL,
    PRIMARY KEY CLUSTERED ([divid] ASC),
    CONSTRAINT [FK_divs_titleid] FOREIGN KEY ([titleid])
		REFERENCES [dbo].[titles] ([titleid]) ON DELETE CASCADE
);

GO

CREATE TABLE [dbo].[articles] (
    [articleid] INT           NOT NULL,
    [titleid]   INT           NOT NULL,
    [name]      NVARCHAR (50) NULL,
    PRIMARY KEY CLUSTERED ([articleid] ASC),
    CONSTRAINT [FK_articles_titleid] FOREIGN KEY ([titleid])
		REFERENCES [dbo].[titles] ([titleid]) ON DELETE CASCADE
);

GO

CREATE TABLE [dbo].[books] (
    [bookid]  INT           NOT NULL,
    [divid]   INT,
    [titleid] INT           NOT NULL,
    [name]    NVARCHAR (50) NULL,
    PRIMARY KEY CLUSTERED ([bookid] ASC),
    CONSTRAINT [FK_books_titleid] FOREIGN KEY ([titleid])
		REFERENCES [dbo].[titles] ([titleid]) ON DELETE CASCADE, 
    CONSTRAINT [FK_books_divid] FOREIGN KEY ([divid])
		REFERENCES [divs]([divid]) ON DELETE NO ACTION
);

If one changes the last ON DELETE clause to ON DELETE CASCADE, you get the dreaded

"Introducing FOREIGN KEY constraint may cause cycles or multiple cascade paths"

error.

I've never run into that with SQLite. With the equivalent schema

BEGIN TRANSACTION;
CREATE TABLE IF NOT EXISTS "titles" (
	"titleid"	INTEGER,
	"title"	TEXT,
	PRIMARY KEY("titleid")
);
CREATE TABLE IF NOT EXISTS "articles" (
	"articleid"	INTEGER,
	"titleid"	INTEGER NOT NULL,
	"name"	TEXT,
	PRIMARY KEY("articleid"),
	FOREIGN KEY("titleid") REFERENCES "titles"("titleid") ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS "divs" (
	"divid"	INTEGER,
	"titleid"	INTEGER NOT NULL,
	"name"	TEXT,
	PRIMARY KEY("divid"),
	FOREIGN KEY("titleid") REFERENCES "titles"("titleid") ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS "books" (
	"bookid"	INTEGER,
	"titleid"	INTEGER NOT NULL,
	"divid"	INTEGER,
	"name"	TEXT,
	PRIMARY KEY("bookid"),
	FOREIGN KEY("titleid") REFERENCES "titles"("titleid") ON DELETE CASCADE,
	FOREIGN KEY("divid") REFERENCES "divs"("divid") ON DELETE CASCADE
);
COMMIT;

cascades from deletion work as advertised.

With SQLServer, the workaround for cascade conflicts is triggers, and I remember having to use triggers for cascades in SQLite because foreign keys weren't enforced. If I remember correctly, when enforcement was implemented, it was done internally with trigger programs anyway.

It occurred to me that I've never run into a cascade conflict with SQLite. Is that because cascade conflicts aren't a problem in SQLite? And if that's the case, is that documented? I couldn't find anything about it in the docs.

Somehow, it seemed clearer when cascades had to be done with triggers (I hate it when things happen that I can't see).

(2) By Keith Medcalf (kmedcalf) on 2022-11-05 00:16:22 in reply to 1 [link] [source]

Microsoft SQL Server is doing the Microsoft thing and stating the obvious just in case you have no clue what it is that you are doing. If by "the dreaded message" you mean that SQL Server is refusing/unable to do what you told it to do, that is a bug in Microsoft SQL Server.

There is absolutely no problem in having multiple foreign key enforcement paths that end up deleting the same child. Provided, of course, that this is what you intend.

SQLite3 is just "doing as it is told". If you told it garbage (garbage-in) then you will get garbage out.

(3) By Mark Benningfield (mbenningfield1) on 2022-11-05 03:36:00 in reply to 2 [link] [source]

I realize I used ON DELETE CASCADE in my example, but the question applies to ON UPDATE CASCADE as well.

SQLServer disallows anything except a single path to a table on a cascade action, because it doesn't even bother to try and find out if there will be any race conditions or cyclic table visitation. It just says "NO".

I gather from your answer that SQLite doesn't attempt to count the paths or detect race conditions or cycles at all. If there is a race, it writes the last winner.

Which I take to mean that my best approach is to use FK's for referential integrity, and explicit triggers for cascades.

(4) By David Raymond (dvdraymond) on 2022-11-07 17:46:16 in reply to 3 [link] [source]

Could you give an example of a race condition or cycle which you think would be problematic? I'm having trouble coming up with an example.