在具有多个级联路径的表结构中,删除操作存在问题。
我知道,主题经常在这里讨论,并且可以找到很好的描述/解决方案,例如在
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
所需外键是(如图所示):
由于存在多个级联路径,因此“在删除集上设置为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