Delete cascade or trigger für Multiple Cascade Path with nullable foreign keys











up vote
1
down vote

favorite
1












I've a problem with the delete-actions in a table-structure with Multiple Cascade Path's.

I know, the theme is often discussed here and a good description/solution can be found e.g. in
https://www.mssqltips.com/sqlservertip/2733/solving-the-sql-server-multiple-cascade-path-issue-with-a-trigger with the solution to use an INSTEAD OF Trigger.



But my table structure is a little bit different because of a nullable-foreign key. The next image shows the desired structure:



                            +-----------------+                       +----------------+
+------(fk not null)----| ProductionSteps |<-----(fk not null)----| CUSTOMER-TABLE |
| delete cascade +-----------------+ delete cascade +----------------+
| ^
V |
+--------------------+ (fk nullable)
| ProductionOrders | on delete set null
+--------------------+ |
^ |
| +-----------------+
+------(fk not null)----| ProcessingData |
delete cascade +-----------------+


And I don't want to use an INSTEAD OF Delete-Trigger for the ProductionSteps-Table because my tables are part of a delivered software product and the customer can extend the system with additional tables, e.g. a CUSTOMER-TABLE with an fk to ProductionSteps (delete cascade).
So by using the INSTEAD OF Trigger the customer has to extend the delivered Trigger for his CUSTOMER-TABLE and therefore the trigger isn't updatable.
Also the customer isn't familiar with triggers.

So I hope there's an alternative solution.



The sql-commands for creating the tables and testdata are added at the end. Inside there are also a detailed description including the error messages.
Environment: SQLServer 2016



The desired foreign keys are (like shown in the image):




  • ProductionSteps: fk to ProductionOrders (not null, delete cascade)

  • ProcessingData 1. fk to ProductionOrders (not null, delete
    cascade) and 2. fk to ProductionSteps (nullable, on delete set
    null
    )


Because of the multiple cascade path's, the "on delete set null" isn't feasible.



==> new version with changes:




  • ProcessingData: fk to ProductionSteps now without delete-action,
    only nullable

  • ProductionSteps: new FOR-DELETE-Trigger to set the
    fk in ProcessingData to NULL


When using this version, it seems, the trigger isn't executed when deleting records from ProductionSteps and so a SQL-Error occures.



Is there an alternative solution other than an INSTEAD OF delete-trigger?



-- -----------------------------------------------------------------------------------------------------
-- Step 1: create table ProductionOrders
-- -----------------------------------------------------------------------------------------------------
CREATE TABLE [ProductionOrders](
[Sequence] [bigint] IDENTITY(1,1) NOT NULL,
[Code] [nvarchar](32) NOT NULL,
CONSTRAINT [PK_ProductionOrders] PRIMARY KEY CLUSTERED
(
[Code] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

-- -----------------------------------------------------------------------------------------------------
-- Step 2a: create table ProductionSteps
-- -----------------------------------------------------------------------------------------------------
CREATE TABLE [ProductionSteps](
[Sequence] [bigint] IDENTITY(1,1) NOT NULL,
[ProductionOrderCode] [nvarchar](32) NOT NULL,
[Code] [nvarchar](32) NOT NULL,
CONSTRAINT [PK_ProductionSteps] PRIMARY KEY CLUSTERED
(
[ProductionOrderCode] ASC,
[Code] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

-- -----------------------------------------------------------------------------------------------------
-- Step 2b: fk ProductionSteps ==> ProductionOrders (not null / delete cascade)
-- -----------------------------------------------------------------------------------------------------
ALTER TABLE [ProductionSteps] WITH CHECK ADD CONSTRAINT [FK_ProductionSteps_ProductionOrder] FOREIGN KEY([ProductionOrderCode])
REFERENCES [ProductionOrders] ([Code])
ON DELETE CASCADE
GO

ALTER TABLE [ProductionSteps] CHECK CONSTRAINT [FK_ProductionSteps_ProductionOrder]
GO

-- -----------------------------------------------------------------------------------------------------
-- Step 3a: create table ProcessingData
-- -----------------------------------------------------------------------------------------------------
CREATE TABLE [ProcessingData](
[Sequence] [bigint] IDENTITY(1,1) NOT NULL,
[ProductionOrderCode] [nvarchar](32) NOT NULL,
[WorkCenterCode] [nvarchar](32) NOT NULL,
[ProductionOrderStepCode] [nvarchar](32) NULL,
[ProductionStepCode] [nvarchar](32) NULL,
[Order] [int] NOT NULL,
CONSTRAINT [PK_ProcessingData] PRIMARY KEY CLUSTERED
(
[ProductionOrderCode] ASC,
[WorkCenterCode] ASC,
[Order] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

-- -----------------------------------------------------------------------------------------------------
-- Step 3b: fk ProcessingData ==> ProductionOrders (not null / delete cascade)
-- -----------------------------------------------------------------------------------------------------
ALTER TABLE [ProcessingData] WITH NOCHECK ADD CONSTRAINT [FK_ProcessingData_ProductionOrders] FOREIGN KEY([ProductionOrderCode])
REFERENCES [ProductionOrders] ([Code])
ON DELETE CASCADE
GO

ALTER TABLE [ProcessingData] CHECK CONSTRAINT [FK_ProcessingData_ProductionOrders]
GO

-- -----------------------------------------------------------------------------------------------------
-- Step 3c: fk ProcessingData ==> ProductionSteps (nullable)
--
-- PROBLEM: "on delete set null" in the next SQL-command isn't feasible !!!
-- executing the SQL-command __WITH__ including "on delete set null" causes an ERROR:
--
-- Msg 1785, Level 16, State 0, Line 2
-- Introducing FOREIGN KEY constraint 'FK_ProcessingData_ProductionSteps' on table 'ProcessingData' may cause cycles or multiple cascade paths.
-- Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
-- Msg 1750, Level 16, State 1, Line 2
-- Could not create constraint or index. See previous errors.
--
-- -----------------------------------------------------------------------------------------------------
ALTER TABLE [ProcessingData] WITH CHECK ADD CONSTRAINT [FK_ProcessingData_ProductionSteps] FOREIGN KEY([ProductionOrderStepCode], [ProductionStepCode])
-- REFERENCES [ProductionSteps] ([ProductionOrderCode], [Code]) on delete set null
REFERENCES [ProductionSteps] ([ProductionOrderCode], [Code])
GO

ALTER TABLE [ProcessingData] CHECK CONSTRAINT [FK_ProcessingData_ProductionSteps]
GO

-- -----------------------------------------------------------------------------------------------------
-- Step 4: create new trigger for table ProductionSteps instead of "on delete set null"
-- -----------------------------------------------------------------------------------------------------
CREATE TRIGGER [TRG_ProcessingData_ProductionStepsDelete]
ON [ProductionSteps]
FOR DELETE
AS
BEGIN
SET NOCOUNT ON
UPDATE [ProcessingData]
SET [ProductionOrderStepCode] = NULL, [ProductionStepCode] = NULL
FROM [ProcessingData] PD
INNER JOIN DELETED D
ON PD.ProductionOrderStepCode = D.ProductionOrderCode
AND PD.ProductionStepCode = D.Code
END
GO

ALTER TABLE [ProductionSteps] ENABLE TRIGGER [TRG_ProcessingData_ProductionStepsDelete]
GO

-- -----------------------------------------------------------------------------------------------------
-- Step 5: create some test-records in tables
-- -----------------------------------------------------------------------------------------------------
INSERT INTO [ProductionOrders] ([Code]) VALUES ('po1')
GO

INSERT INTO [ProductionSteps] ([ProductionOrderCode] ,[Code]) VALUES ('po1', 'ps1')
INSERT INTO [ProductionSteps] ([ProductionOrderCode] ,[Code]) VALUES ('po1', 'ps2')
GO

INSERT INTO [ProcessingData] ([ProductionOrderCode], [WorkCenterCode], [ProductionOrderStepCode],[ProductionStepCode],[Order])
VALUES ('po1', 'wc1', 'po1', 'ps1', 1)
INSERT INTO [ProcessingData] ([ProductionOrderCode], [WorkCenterCode], [ProductionOrderStepCode],[ProductionStepCode],[Order])
VALUES ('po1', 'wc1', 'po1', 'ps2', 2)
GO

-- -----------------------------------------------------------------------------------------------------
-- Step 6: Test: delete all records in table ProductionSteps
--
-- Executing the next SQL-Command causes an ERROR!!! It seems, the trigger isn't executed
--
-- ERROR:
-- Msg 547, Level 16, State 0, Line 1
-- The DELETE statement conflicted with the REFERENCE constraint "FK_ProcessingData_ProductionSteps".
-- The conflict occurred in database "TEST_DeleteSetNull", table "dbo.ProcessingData".
--
-- -----------------------------------------------------------------------------------------------------
DELETE FROM [ProductionSteps]
GO









share|improve this question


























    up vote
    1
    down vote

    favorite
    1












    I've a problem with the delete-actions in a table-structure with Multiple Cascade Path's.

    I know, the theme is often discussed here and a good description/solution can be found e.g. in
    https://www.mssqltips.com/sqlservertip/2733/solving-the-sql-server-multiple-cascade-path-issue-with-a-trigger with the solution to use an INSTEAD OF Trigger.



    But my table structure is a little bit different because of a nullable-foreign key. The next image shows the desired structure:



                                +-----------------+                       +----------------+
    +------(fk not null)----| ProductionSteps |<-----(fk not null)----| CUSTOMER-TABLE |
    | delete cascade +-----------------+ delete cascade +----------------+
    | ^
    V |
    +--------------------+ (fk nullable)
    | ProductionOrders | on delete set null
    +--------------------+ |
    ^ |
    | +-----------------+
    +------(fk not null)----| ProcessingData |
    delete cascade +-----------------+


    And I don't want to use an INSTEAD OF Delete-Trigger for the ProductionSteps-Table because my tables are part of a delivered software product and the customer can extend the system with additional tables, e.g. a CUSTOMER-TABLE with an fk to ProductionSteps (delete cascade).
    So by using the INSTEAD OF Trigger the customer has to extend the delivered Trigger for his CUSTOMER-TABLE and therefore the trigger isn't updatable.
    Also the customer isn't familiar with triggers.

    So I hope there's an alternative solution.



    The sql-commands for creating the tables and testdata are added at the end. Inside there are also a detailed description including the error messages.
    Environment: SQLServer 2016



    The desired foreign keys are (like shown in the image):




    • ProductionSteps: fk to ProductionOrders (not null, delete cascade)

    • ProcessingData 1. fk to ProductionOrders (not null, delete
      cascade) and 2. fk to ProductionSteps (nullable, on delete set
      null
      )


    Because of the multiple cascade path's, the "on delete set null" isn't feasible.



    ==> new version with changes:




    • ProcessingData: fk to ProductionSteps now without delete-action,
      only nullable

    • ProductionSteps: new FOR-DELETE-Trigger to set the
      fk in ProcessingData to NULL


    When using this version, it seems, the trigger isn't executed when deleting records from ProductionSteps and so a SQL-Error occures.



    Is there an alternative solution other than an INSTEAD OF delete-trigger?



    -- -----------------------------------------------------------------------------------------------------
    -- Step 1: create table ProductionOrders
    -- -----------------------------------------------------------------------------------------------------
    CREATE TABLE [ProductionOrders](
    [Sequence] [bigint] IDENTITY(1,1) NOT NULL,
    [Code] [nvarchar](32) NOT NULL,
    CONSTRAINT [PK_ProductionOrders] PRIMARY KEY CLUSTERED
    (
    [Code] ASC
    )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
    ) ON [PRIMARY]

    GO

    -- -----------------------------------------------------------------------------------------------------
    -- Step 2a: create table ProductionSteps
    -- -----------------------------------------------------------------------------------------------------
    CREATE TABLE [ProductionSteps](
    [Sequence] [bigint] IDENTITY(1,1) NOT NULL,
    [ProductionOrderCode] [nvarchar](32) NOT NULL,
    [Code] [nvarchar](32) NOT NULL,
    CONSTRAINT [PK_ProductionSteps] PRIMARY KEY CLUSTERED
    (
    [ProductionOrderCode] ASC,
    [Code] ASC
    )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
    ) ON [PRIMARY]
    GO

    -- -----------------------------------------------------------------------------------------------------
    -- Step 2b: fk ProductionSteps ==> ProductionOrders (not null / delete cascade)
    -- -----------------------------------------------------------------------------------------------------
    ALTER TABLE [ProductionSteps] WITH CHECK ADD CONSTRAINT [FK_ProductionSteps_ProductionOrder] FOREIGN KEY([ProductionOrderCode])
    REFERENCES [ProductionOrders] ([Code])
    ON DELETE CASCADE
    GO

    ALTER TABLE [ProductionSteps] CHECK CONSTRAINT [FK_ProductionSteps_ProductionOrder]
    GO

    -- -----------------------------------------------------------------------------------------------------
    -- Step 3a: create table ProcessingData
    -- -----------------------------------------------------------------------------------------------------
    CREATE TABLE [ProcessingData](
    [Sequence] [bigint] IDENTITY(1,1) NOT NULL,
    [ProductionOrderCode] [nvarchar](32) NOT NULL,
    [WorkCenterCode] [nvarchar](32) NOT NULL,
    [ProductionOrderStepCode] [nvarchar](32) NULL,
    [ProductionStepCode] [nvarchar](32) NULL,
    [Order] [int] NOT NULL,
    CONSTRAINT [PK_ProcessingData] PRIMARY KEY CLUSTERED
    (
    [ProductionOrderCode] ASC,
    [WorkCenterCode] ASC,
    [Order] ASC
    )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
    ) ON [PRIMARY]
    GO

    -- -----------------------------------------------------------------------------------------------------
    -- Step 3b: fk ProcessingData ==> ProductionOrders (not null / delete cascade)
    -- -----------------------------------------------------------------------------------------------------
    ALTER TABLE [ProcessingData] WITH NOCHECK ADD CONSTRAINT [FK_ProcessingData_ProductionOrders] FOREIGN KEY([ProductionOrderCode])
    REFERENCES [ProductionOrders] ([Code])
    ON DELETE CASCADE
    GO

    ALTER TABLE [ProcessingData] CHECK CONSTRAINT [FK_ProcessingData_ProductionOrders]
    GO

    -- -----------------------------------------------------------------------------------------------------
    -- Step 3c: fk ProcessingData ==> ProductionSteps (nullable)
    --
    -- PROBLEM: "on delete set null" in the next SQL-command isn't feasible !!!
    -- executing the SQL-command __WITH__ including "on delete set null" causes an ERROR:
    --
    -- Msg 1785, Level 16, State 0, Line 2
    -- Introducing FOREIGN KEY constraint 'FK_ProcessingData_ProductionSteps' on table 'ProcessingData' may cause cycles or multiple cascade paths.
    -- Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
    -- Msg 1750, Level 16, State 1, Line 2
    -- Could not create constraint or index. See previous errors.
    --
    -- -----------------------------------------------------------------------------------------------------
    ALTER TABLE [ProcessingData] WITH CHECK ADD CONSTRAINT [FK_ProcessingData_ProductionSteps] FOREIGN KEY([ProductionOrderStepCode], [ProductionStepCode])
    -- REFERENCES [ProductionSteps] ([ProductionOrderCode], [Code]) on delete set null
    REFERENCES [ProductionSteps] ([ProductionOrderCode], [Code])
    GO

    ALTER TABLE [ProcessingData] CHECK CONSTRAINT [FK_ProcessingData_ProductionSteps]
    GO

    -- -----------------------------------------------------------------------------------------------------
    -- Step 4: create new trigger for table ProductionSteps instead of "on delete set null"
    -- -----------------------------------------------------------------------------------------------------
    CREATE TRIGGER [TRG_ProcessingData_ProductionStepsDelete]
    ON [ProductionSteps]
    FOR DELETE
    AS
    BEGIN
    SET NOCOUNT ON
    UPDATE [ProcessingData]
    SET [ProductionOrderStepCode] = NULL, [ProductionStepCode] = NULL
    FROM [ProcessingData] PD
    INNER JOIN DELETED D
    ON PD.ProductionOrderStepCode = D.ProductionOrderCode
    AND PD.ProductionStepCode = D.Code
    END
    GO

    ALTER TABLE [ProductionSteps] ENABLE TRIGGER [TRG_ProcessingData_ProductionStepsDelete]
    GO

    -- -----------------------------------------------------------------------------------------------------
    -- Step 5: create some test-records in tables
    -- -----------------------------------------------------------------------------------------------------
    INSERT INTO [ProductionOrders] ([Code]) VALUES ('po1')
    GO

    INSERT INTO [ProductionSteps] ([ProductionOrderCode] ,[Code]) VALUES ('po1', 'ps1')
    INSERT INTO [ProductionSteps] ([ProductionOrderCode] ,[Code]) VALUES ('po1', 'ps2')
    GO

    INSERT INTO [ProcessingData] ([ProductionOrderCode], [WorkCenterCode], [ProductionOrderStepCode],[ProductionStepCode],[Order])
    VALUES ('po1', 'wc1', 'po1', 'ps1', 1)
    INSERT INTO [ProcessingData] ([ProductionOrderCode], [WorkCenterCode], [ProductionOrderStepCode],[ProductionStepCode],[Order])
    VALUES ('po1', 'wc1', 'po1', 'ps2', 2)
    GO

    -- -----------------------------------------------------------------------------------------------------
    -- Step 6: Test: delete all records in table ProductionSteps
    --
    -- Executing the next SQL-Command causes an ERROR!!! It seems, the trigger isn't executed
    --
    -- ERROR:
    -- Msg 547, Level 16, State 0, Line 1
    -- The DELETE statement conflicted with the REFERENCE constraint "FK_ProcessingData_ProductionSteps".
    -- The conflict occurred in database "TEST_DeleteSetNull", table "dbo.ProcessingData".
    --
    -- -----------------------------------------------------------------------------------------------------
    DELETE FROM [ProductionSteps]
    GO









    share|improve this question
























      up vote
      1
      down vote

      favorite
      1









      up vote
      1
      down vote

      favorite
      1






      1





      I've a problem with the delete-actions in a table-structure with Multiple Cascade Path's.

      I know, the theme is often discussed here and a good description/solution can be found e.g. in
      https://www.mssqltips.com/sqlservertip/2733/solving-the-sql-server-multiple-cascade-path-issue-with-a-trigger with the solution to use an INSTEAD OF Trigger.



      But my table structure is a little bit different because of a nullable-foreign key. The next image shows the desired structure:



                                  +-----------------+                       +----------------+
      +------(fk not null)----| ProductionSteps |<-----(fk not null)----| CUSTOMER-TABLE |
      | delete cascade +-----------------+ delete cascade +----------------+
      | ^
      V |
      +--------------------+ (fk nullable)
      | ProductionOrders | on delete set null
      +--------------------+ |
      ^ |
      | +-----------------+
      +------(fk not null)----| ProcessingData |
      delete cascade +-----------------+


      And I don't want to use an INSTEAD OF Delete-Trigger for the ProductionSteps-Table because my tables are part of a delivered software product and the customer can extend the system with additional tables, e.g. a CUSTOMER-TABLE with an fk to ProductionSteps (delete cascade).
      So by using the INSTEAD OF Trigger the customer has to extend the delivered Trigger for his CUSTOMER-TABLE and therefore the trigger isn't updatable.
      Also the customer isn't familiar with triggers.

      So I hope there's an alternative solution.



      The sql-commands for creating the tables and testdata are added at the end. Inside there are also a detailed description including the error messages.
      Environment: SQLServer 2016



      The desired foreign keys are (like shown in the image):




      • ProductionSteps: fk to ProductionOrders (not null, delete cascade)

      • ProcessingData 1. fk to ProductionOrders (not null, delete
        cascade) and 2. fk to ProductionSteps (nullable, on delete set
        null
        )


      Because of the multiple cascade path's, the "on delete set null" isn't feasible.



      ==> new version with changes:




      • ProcessingData: fk to ProductionSteps now without delete-action,
        only nullable

      • ProductionSteps: new FOR-DELETE-Trigger to set the
        fk in ProcessingData to NULL


      When using this version, it seems, the trigger isn't executed when deleting records from ProductionSteps and so a SQL-Error occures.



      Is there an alternative solution other than an INSTEAD OF delete-trigger?



      -- -----------------------------------------------------------------------------------------------------
      -- Step 1: create table ProductionOrders
      -- -----------------------------------------------------------------------------------------------------
      CREATE TABLE [ProductionOrders](
      [Sequence] [bigint] IDENTITY(1,1) NOT NULL,
      [Code] [nvarchar](32) NOT NULL,
      CONSTRAINT [PK_ProductionOrders] PRIMARY KEY CLUSTERED
      (
      [Code] ASC
      )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
      ) ON [PRIMARY]

      GO

      -- -----------------------------------------------------------------------------------------------------
      -- Step 2a: create table ProductionSteps
      -- -----------------------------------------------------------------------------------------------------
      CREATE TABLE [ProductionSteps](
      [Sequence] [bigint] IDENTITY(1,1) NOT NULL,
      [ProductionOrderCode] [nvarchar](32) NOT NULL,
      [Code] [nvarchar](32) NOT NULL,
      CONSTRAINT [PK_ProductionSteps] PRIMARY KEY CLUSTERED
      (
      [ProductionOrderCode] ASC,
      [Code] ASC
      )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
      ) ON [PRIMARY]
      GO

      -- -----------------------------------------------------------------------------------------------------
      -- Step 2b: fk ProductionSteps ==> ProductionOrders (not null / delete cascade)
      -- -----------------------------------------------------------------------------------------------------
      ALTER TABLE [ProductionSteps] WITH CHECK ADD CONSTRAINT [FK_ProductionSteps_ProductionOrder] FOREIGN KEY([ProductionOrderCode])
      REFERENCES [ProductionOrders] ([Code])
      ON DELETE CASCADE
      GO

      ALTER TABLE [ProductionSteps] CHECK CONSTRAINT [FK_ProductionSteps_ProductionOrder]
      GO

      -- -----------------------------------------------------------------------------------------------------
      -- Step 3a: create table ProcessingData
      -- -----------------------------------------------------------------------------------------------------
      CREATE TABLE [ProcessingData](
      [Sequence] [bigint] IDENTITY(1,1) NOT NULL,
      [ProductionOrderCode] [nvarchar](32) NOT NULL,
      [WorkCenterCode] [nvarchar](32) NOT NULL,
      [ProductionOrderStepCode] [nvarchar](32) NULL,
      [ProductionStepCode] [nvarchar](32) NULL,
      [Order] [int] NOT NULL,
      CONSTRAINT [PK_ProcessingData] PRIMARY KEY CLUSTERED
      (
      [ProductionOrderCode] ASC,
      [WorkCenterCode] ASC,
      [Order] ASC
      )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
      ) ON [PRIMARY]
      GO

      -- -----------------------------------------------------------------------------------------------------
      -- Step 3b: fk ProcessingData ==> ProductionOrders (not null / delete cascade)
      -- -----------------------------------------------------------------------------------------------------
      ALTER TABLE [ProcessingData] WITH NOCHECK ADD CONSTRAINT [FK_ProcessingData_ProductionOrders] FOREIGN KEY([ProductionOrderCode])
      REFERENCES [ProductionOrders] ([Code])
      ON DELETE CASCADE
      GO

      ALTER TABLE [ProcessingData] CHECK CONSTRAINT [FK_ProcessingData_ProductionOrders]
      GO

      -- -----------------------------------------------------------------------------------------------------
      -- Step 3c: fk ProcessingData ==> ProductionSteps (nullable)
      --
      -- PROBLEM: "on delete set null" in the next SQL-command isn't feasible !!!
      -- executing the SQL-command __WITH__ including "on delete set null" causes an ERROR:
      --
      -- Msg 1785, Level 16, State 0, Line 2
      -- Introducing FOREIGN KEY constraint 'FK_ProcessingData_ProductionSteps' on table 'ProcessingData' may cause cycles or multiple cascade paths.
      -- Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
      -- Msg 1750, Level 16, State 1, Line 2
      -- Could not create constraint or index. See previous errors.
      --
      -- -----------------------------------------------------------------------------------------------------
      ALTER TABLE [ProcessingData] WITH CHECK ADD CONSTRAINT [FK_ProcessingData_ProductionSteps] FOREIGN KEY([ProductionOrderStepCode], [ProductionStepCode])
      -- REFERENCES [ProductionSteps] ([ProductionOrderCode], [Code]) on delete set null
      REFERENCES [ProductionSteps] ([ProductionOrderCode], [Code])
      GO

      ALTER TABLE [ProcessingData] CHECK CONSTRAINT [FK_ProcessingData_ProductionSteps]
      GO

      -- -----------------------------------------------------------------------------------------------------
      -- Step 4: create new trigger for table ProductionSteps instead of "on delete set null"
      -- -----------------------------------------------------------------------------------------------------
      CREATE TRIGGER [TRG_ProcessingData_ProductionStepsDelete]
      ON [ProductionSteps]
      FOR DELETE
      AS
      BEGIN
      SET NOCOUNT ON
      UPDATE [ProcessingData]
      SET [ProductionOrderStepCode] = NULL, [ProductionStepCode] = NULL
      FROM [ProcessingData] PD
      INNER JOIN DELETED D
      ON PD.ProductionOrderStepCode = D.ProductionOrderCode
      AND PD.ProductionStepCode = D.Code
      END
      GO

      ALTER TABLE [ProductionSteps] ENABLE TRIGGER [TRG_ProcessingData_ProductionStepsDelete]
      GO

      -- -----------------------------------------------------------------------------------------------------
      -- Step 5: create some test-records in tables
      -- -----------------------------------------------------------------------------------------------------
      INSERT INTO [ProductionOrders] ([Code]) VALUES ('po1')
      GO

      INSERT INTO [ProductionSteps] ([ProductionOrderCode] ,[Code]) VALUES ('po1', 'ps1')
      INSERT INTO [ProductionSteps] ([ProductionOrderCode] ,[Code]) VALUES ('po1', 'ps2')
      GO

      INSERT INTO [ProcessingData] ([ProductionOrderCode], [WorkCenterCode], [ProductionOrderStepCode],[ProductionStepCode],[Order])
      VALUES ('po1', 'wc1', 'po1', 'ps1', 1)
      INSERT INTO [ProcessingData] ([ProductionOrderCode], [WorkCenterCode], [ProductionOrderStepCode],[ProductionStepCode],[Order])
      VALUES ('po1', 'wc1', 'po1', 'ps2', 2)
      GO

      -- -----------------------------------------------------------------------------------------------------
      -- Step 6: Test: delete all records in table ProductionSteps
      --
      -- Executing the next SQL-Command causes an ERROR!!! It seems, the trigger isn't executed
      --
      -- ERROR:
      -- Msg 547, Level 16, State 0, Line 1
      -- The DELETE statement conflicted with the REFERENCE constraint "FK_ProcessingData_ProductionSteps".
      -- The conflict occurred in database "TEST_DeleteSetNull", table "dbo.ProcessingData".
      --
      -- -----------------------------------------------------------------------------------------------------
      DELETE FROM [ProductionSteps]
      GO









      share|improve this question













      I've a problem with the delete-actions in a table-structure with Multiple Cascade Path's.

      I know, the theme is often discussed here and a good description/solution can be found e.g. in
      https://www.mssqltips.com/sqlservertip/2733/solving-the-sql-server-multiple-cascade-path-issue-with-a-trigger with the solution to use an INSTEAD OF Trigger.



      But my table structure is a little bit different because of a nullable-foreign key. The next image shows the desired structure:



                                  +-----------------+                       +----------------+
      +------(fk not null)----| ProductionSteps |<-----(fk not null)----| CUSTOMER-TABLE |
      | delete cascade +-----------------+ delete cascade +----------------+
      | ^
      V |
      +--------------------+ (fk nullable)
      | ProductionOrders | on delete set null
      +--------------------+ |
      ^ |
      | +-----------------+
      +------(fk not null)----| ProcessingData |
      delete cascade +-----------------+


      And I don't want to use an INSTEAD OF Delete-Trigger for the ProductionSteps-Table because my tables are part of a delivered software product and the customer can extend the system with additional tables, e.g. a CUSTOMER-TABLE with an fk to ProductionSteps (delete cascade).
      So by using the INSTEAD OF Trigger the customer has to extend the delivered Trigger for his CUSTOMER-TABLE and therefore the trigger isn't updatable.
      Also the customer isn't familiar with triggers.

      So I hope there's an alternative solution.



      The sql-commands for creating the tables and testdata are added at the end. Inside there are also a detailed description including the error messages.
      Environment: SQLServer 2016



      The desired foreign keys are (like shown in the image):




      • ProductionSteps: fk to ProductionOrders (not null, delete cascade)

      • ProcessingData 1. fk to ProductionOrders (not null, delete
        cascade) and 2. fk to ProductionSteps (nullable, on delete set
        null
        )


      Because of the multiple cascade path's, the "on delete set null" isn't feasible.



      ==> new version with changes:




      • ProcessingData: fk to ProductionSteps now without delete-action,
        only nullable

      • ProductionSteps: new FOR-DELETE-Trigger to set the
        fk in ProcessingData to NULL


      When using this version, it seems, the trigger isn't executed when deleting records from ProductionSteps and so a SQL-Error occures.



      Is there an alternative solution other than an INSTEAD OF delete-trigger?



      -- -----------------------------------------------------------------------------------------------------
      -- Step 1: create table ProductionOrders
      -- -----------------------------------------------------------------------------------------------------
      CREATE TABLE [ProductionOrders](
      [Sequence] [bigint] IDENTITY(1,1) NOT NULL,
      [Code] [nvarchar](32) NOT NULL,
      CONSTRAINT [PK_ProductionOrders] PRIMARY KEY CLUSTERED
      (
      [Code] ASC
      )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
      ) ON [PRIMARY]

      GO

      -- -----------------------------------------------------------------------------------------------------
      -- Step 2a: create table ProductionSteps
      -- -----------------------------------------------------------------------------------------------------
      CREATE TABLE [ProductionSteps](
      [Sequence] [bigint] IDENTITY(1,1) NOT NULL,
      [ProductionOrderCode] [nvarchar](32) NOT NULL,
      [Code] [nvarchar](32) NOT NULL,
      CONSTRAINT [PK_ProductionSteps] PRIMARY KEY CLUSTERED
      (
      [ProductionOrderCode] ASC,
      [Code] ASC
      )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
      ) ON [PRIMARY]
      GO

      -- -----------------------------------------------------------------------------------------------------
      -- Step 2b: fk ProductionSteps ==> ProductionOrders (not null / delete cascade)
      -- -----------------------------------------------------------------------------------------------------
      ALTER TABLE [ProductionSteps] WITH CHECK ADD CONSTRAINT [FK_ProductionSteps_ProductionOrder] FOREIGN KEY([ProductionOrderCode])
      REFERENCES [ProductionOrders] ([Code])
      ON DELETE CASCADE
      GO

      ALTER TABLE [ProductionSteps] CHECK CONSTRAINT [FK_ProductionSteps_ProductionOrder]
      GO

      -- -----------------------------------------------------------------------------------------------------
      -- Step 3a: create table ProcessingData
      -- -----------------------------------------------------------------------------------------------------
      CREATE TABLE [ProcessingData](
      [Sequence] [bigint] IDENTITY(1,1) NOT NULL,
      [ProductionOrderCode] [nvarchar](32) NOT NULL,
      [WorkCenterCode] [nvarchar](32) NOT NULL,
      [ProductionOrderStepCode] [nvarchar](32) NULL,
      [ProductionStepCode] [nvarchar](32) NULL,
      [Order] [int] NOT NULL,
      CONSTRAINT [PK_ProcessingData] PRIMARY KEY CLUSTERED
      (
      [ProductionOrderCode] ASC,
      [WorkCenterCode] ASC,
      [Order] ASC
      )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
      ) ON [PRIMARY]
      GO

      -- -----------------------------------------------------------------------------------------------------
      -- Step 3b: fk ProcessingData ==> ProductionOrders (not null / delete cascade)
      -- -----------------------------------------------------------------------------------------------------
      ALTER TABLE [ProcessingData] WITH NOCHECK ADD CONSTRAINT [FK_ProcessingData_ProductionOrders] FOREIGN KEY([ProductionOrderCode])
      REFERENCES [ProductionOrders] ([Code])
      ON DELETE CASCADE
      GO

      ALTER TABLE [ProcessingData] CHECK CONSTRAINT [FK_ProcessingData_ProductionOrders]
      GO

      -- -----------------------------------------------------------------------------------------------------
      -- Step 3c: fk ProcessingData ==> ProductionSteps (nullable)
      --
      -- PROBLEM: "on delete set null" in the next SQL-command isn't feasible !!!
      -- executing the SQL-command __WITH__ including "on delete set null" causes an ERROR:
      --
      -- Msg 1785, Level 16, State 0, Line 2
      -- Introducing FOREIGN KEY constraint 'FK_ProcessingData_ProductionSteps' on table 'ProcessingData' may cause cycles or multiple cascade paths.
      -- Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
      -- Msg 1750, Level 16, State 1, Line 2
      -- Could not create constraint or index. See previous errors.
      --
      -- -----------------------------------------------------------------------------------------------------
      ALTER TABLE [ProcessingData] WITH CHECK ADD CONSTRAINT [FK_ProcessingData_ProductionSteps] FOREIGN KEY([ProductionOrderStepCode], [ProductionStepCode])
      -- REFERENCES [ProductionSteps] ([ProductionOrderCode], [Code]) on delete set null
      REFERENCES [ProductionSteps] ([ProductionOrderCode], [Code])
      GO

      ALTER TABLE [ProcessingData] CHECK CONSTRAINT [FK_ProcessingData_ProductionSteps]
      GO

      -- -----------------------------------------------------------------------------------------------------
      -- Step 4: create new trigger for table ProductionSteps instead of "on delete set null"
      -- -----------------------------------------------------------------------------------------------------
      CREATE TRIGGER [TRG_ProcessingData_ProductionStepsDelete]
      ON [ProductionSteps]
      FOR DELETE
      AS
      BEGIN
      SET NOCOUNT ON
      UPDATE [ProcessingData]
      SET [ProductionOrderStepCode] = NULL, [ProductionStepCode] = NULL
      FROM [ProcessingData] PD
      INNER JOIN DELETED D
      ON PD.ProductionOrderStepCode = D.ProductionOrderCode
      AND PD.ProductionStepCode = D.Code
      END
      GO

      ALTER TABLE [ProductionSteps] ENABLE TRIGGER [TRG_ProcessingData_ProductionStepsDelete]
      GO

      -- -----------------------------------------------------------------------------------------------------
      -- Step 5: create some test-records in tables
      -- -----------------------------------------------------------------------------------------------------
      INSERT INTO [ProductionOrders] ([Code]) VALUES ('po1')
      GO

      INSERT INTO [ProductionSteps] ([ProductionOrderCode] ,[Code]) VALUES ('po1', 'ps1')
      INSERT INTO [ProductionSteps] ([ProductionOrderCode] ,[Code]) VALUES ('po1', 'ps2')
      GO

      INSERT INTO [ProcessingData] ([ProductionOrderCode], [WorkCenterCode], [ProductionOrderStepCode],[ProductionStepCode],[Order])
      VALUES ('po1', 'wc1', 'po1', 'ps1', 1)
      INSERT INTO [ProcessingData] ([ProductionOrderCode], [WorkCenterCode], [ProductionOrderStepCode],[ProductionStepCode],[Order])
      VALUES ('po1', 'wc1', 'po1', 'ps2', 2)
      GO

      -- -----------------------------------------------------------------------------------------------------
      -- Step 6: Test: delete all records in table ProductionSteps
      --
      -- Executing the next SQL-Command causes an ERROR!!! It seems, the trigger isn't executed
      --
      -- ERROR:
      -- Msg 547, Level 16, State 0, Line 1
      -- The DELETE statement conflicted with the REFERENCE constraint "FK_ProcessingData_ProductionSteps".
      -- The conflict occurred in database "TEST_DeleteSetNull", table "dbo.ProcessingData".
      --
      -- -----------------------------------------------------------------------------------------------------
      DELETE FROM [ProductionSteps]
      GO






      sql-server database-design






      share|improve this question













      share|improve this question











      share|improve this question




      share|improve this question










      asked Nov 9 at 12:51









      Juergen2018

      62




      62





























          active

          oldest

          votes











          Your Answer






          StackExchange.ifUsing("editor", function () {
          StackExchange.using("externalEditor", function () {
          StackExchange.using("snippets", function () {
          StackExchange.snippets.init();
          });
          });
          }, "code-snippets");

          StackExchange.ready(function() {
          var channelOptions = {
          tags: "".split(" "),
          id: "1"
          };
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function() {
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled) {
          StackExchange.using("snippets", function() {
          createEditor();
          });
          }
          else {
          createEditor();
          }
          });

          function createEditor() {
          StackExchange.prepareEditor({
          heartbeatType: 'answer',
          convertImagesToLinks: true,
          noModals: true,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: 10,
          bindNavPrevention: true,
          postfix: "",
          imageUploader: {
          brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
          contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
          allowUrls: true
          },
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          });


          }
          });














          draft saved

          draft discarded


















          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53226042%2fdelete-cascade-or-trigger-f%25c3%25bcr-multiple-cascade-path-with-nullable-foreign-keys%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown






























          active

          oldest

          votes













          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes
















          draft saved

          draft discarded




















































          Thanks for contributing an answer to Stack Overflow!


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          To learn more, see our tips on writing great answers.





          Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


          Please pay close attention to the following guidance:


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          To learn more, see our tips on writing great answers.




          draft saved


          draft discarded














          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53226042%2fdelete-cascade-or-trigger-f%25c3%25bcr-multiple-cascade-path-with-nullable-foreign-keys%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown





















































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown

































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown







          這個網誌中的熱門文章

          Xamarin.form Move up view when keyboard appear

          Post-Redirect-Get with Spring WebFlux and Thymeleaf

          Anylogic : not able to use stopDelay()