删除级联或使用可为空的外键触发多级联路径

时间:2018-11-09 12:51:03

标签: sql-server database-design

在具有多个级联路径的表结构中,删除操作存在问题。
我知道,主题经常在这里讨论,并且可以找到很好的描述/解决方案,例如在
https://www.mssqltips.com/sqlservertip/2733/solving-the-sql-server-multiple-cascade-path-issue-with-a-trigger以及使用INSTEAD OF触发器的解决方案。

但是由于可空-外键,我的表结构有所不同。下一张图片显示了所需结构:

                            +-----------------+                       +----------------+
    +------(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    +-----------------+

我不想对ProductionSteps-Table使用INSTEAD OF Delete-Trigger,因为我的表是已交付的软件产品的一部分,并且客户可以使用其他表来扩展系统,例如一个带有fk到ProductionSteps的客户表(删除级联)。 因此,通过使用INSTEAD OF触发器,客户必须为其客户表扩展已交付的触发器,因此该触发器是不可更新的。 另外,客户也不熟悉触发器。
所以我希望有替代的解决方案。

最后将添加用于创建表和测试数据的sql命令。里面还有详细的描述,包括错误消息。 环境:SQLServer 2016

所需外键是(如图所示):

  • ProductionSteps:fk到ProductionOrders(不为null,删除级联)
  • ProcessingData 1. fk到ProductionOrders(不为null,删除 级联)和2. fk到ProductionSteps(为空,在删除集上) null

由于存在多个级联路径,因此“在删除集上设置为null”是不可行的。

==>具有更改的新版本:

  • ProcessingData:现在可以直接转到ProductionSteps ,没有删除操作, 只能为空
  • ProductionSteps:新的 FOR-DELETE-Trigger 用于设置 处理数据中的fk为NULL

使用此版本时,似乎从ProductionSteps删除记录时未执行触发器,因此发生SQL错误。

除了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

0 个答案:

没有答案