'Two foreign keys reference one table - ON UPDATE SET NULL doesn't work

I've got two foreign keys in a table. Let's assume that table is called News and has foreign keys updatedById and createdById, both of which point to userId in table Users.

Now I want to set to NULL foreign keys when user is deleted, but when I try to set ON DELETE SET NULL in that relationships I get:

Introducing FOREIGN KEY constraint 'FK_News_Users' on table 'News' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.

I don't understand why both foreign keys can't set to null?



Solution 1:[1]

Multiple Cascading Actions

The series of cascading referential actions triggered by a single DELETE or UPDATE must form a tree that contains no circular references. No table can appear more than one time in the list of all cascading referential actions that result from the DELETE or UPDATE. Also, the tree of cascading referential actions must not have more than one path to any specified table. Any branch of the tree is ended when it encounters a table for which NO ACTION has been specified or is the default.

Possibly in situations like this you might want to consider to implement functionality to delete user logically rather then physically (e.g. by introducing a flag field Active or Deleted in Users table). That way all relationships stay intact and can be analyzed retrospectively.

But if you still need to implement ON DELETE SET NULL for both FK's you can use a FOR DELETE trigger on User table like this:

CREATE TRIGGER Users_News_Delete_Trigger 
ON Users FOR DELETE
AS BEGIN
    UPDATE News SET createdById = NULL 
     WHERE createdById = DELETED.id;
    UPDATE News SET updatedById = NULL 
     WHERE updatedById = DELETED.id;
END

Solution 2:[2]

One alternative is to create a cross reference table between table A and table B where each entry is A.ID and B.ID and B.ID has a foreign key to B. Then you can simply CASCADE deletes to the cross reference. You will need to put a third field in your cross reference to state the unique purpose of reference such as

[NewsID] INT NOT NULL DEFAULT 0,
[UsersID] INT NOT NULL DEFAULT 0,
[IsCreatedBy] bit NOT NULL DEFAULT 0

Naturally, you would then take those fields out of table A. The left join will then give you null for those fields if they are missing.

Solution 3:[3]

I don't think it's possible (in SQL Server) to do it on 2 or more FK constraints on the same table, pointing to the same FK.

Normally in situations like this you'd rather delete user logically then physically by introducing a flag field (e.g. Active or Deleted). That way all relationships stay intact and can be analyzed retrospectively. --- peterm

If you want to stick with the original idea of setting NULL, a way around the problem would be to handle your deletion of users in a stored procedure and have it perform the updates immediately afterwards.

CREATE PROCEDURE sp_DeleteUser 
    @UserId INT
AS
BEGIN
    SET NOCOUNT ON;

    DELETE FROM Users WHERE Id = @UserId;

    UPDATE News SET created_byId = NULL WHERE created_byId = @UserId;

    UPDATE News SET updated_byId = NULL WHERE created_byId = @UserId;
END
GO

Solution 4:[4]

Multiple Delete/Update Rules when multiple foreign keys reference one table:

Years after you asked this question, I had the same problem.

I have Currencies and Products tables (i simplified for sharing) in my database as in the diagram:

multiple foreign keys reference one table

I needed to SET NULL both columns BuyingCurrencyId and SellingCurrencyId when I delete a record from table Currencies. SQL Server does not support multiple INSERT And UPDATE Specification in a structure like this.

In this case, a temporary solution can be produced by adding a boolean IsDeleted column to table Currencies. But in a large database, even if the DeleteRule || UpdateRule specifications is passed around with such a tricky solution in a table, it will definitely be necessary somewhere in the database.

I got the following conflict occurred in database error in all of the triggers I created before:

A problem occurred attempting to delete row n. The DELETE statement conflicted with the REFERENCE constraint foreign key.

And finally I was able to solve the problem by creating the following trigger:


USE [FooBarDatabase]
GO

--DROP TRIGGER accounting.Currency_Delete_Trigger
--GO

CREATE TRIGGER accounting.Currency_Delete_Trigger
ON accounting.Currencies INSTEAD OF DELETE
AS BEGIN
    UPDATE stock.Products
      SET BuyingCurrencyId = NULL,
          SellingCurrencyId = NULL
      WHERE BuyingCurrencyId in (SELECT Id FROM deleted)
      AND   SellingCurrencyId in (SELECT Id FROM deleted);
    DELETE accounting.Currencies 
      WHERE Id in (SELECT Id FROM deleted);
END
GO

ALTER TABLE [accounting].[Currencies] 
   ENABLE TRIGGER [Currency_Delete_Trigger]
GO

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3
Solution 4