我使用下表来实现子类型,这是一种非常常见的方法:
CREATE TABLE dbo.Vehicles(
ID INT NOT NULL,
[Type] VARCHAR(5) NOT NULL,
CONSTRAINT Vehicles_PK PRIMARY KEY(ID),
CONSTRAINT Vehicles_UNQ_ID_Type UNIQUE(ID, [Type]),
CONSTRAINT Vehicles_CHK_ValidTypes CHECK([Type] IN ('Car', 'Truck'))
);
GO
CREATE TABLE dbo.Cars(ID INT NOT NULL,
[Type] AS CAST('Car' AS VARCHAR(5)) PERSISTED,
OtherData VARCHAR(10) NULL,
CONSTRAINT Cars_PK PRIMARY KEY(ID),
CONSTRAINT Cars_FK_Vehicles FOREIGN KEY(ID, [Type])
REFERENCES dbo.Vehicles(ID, [Type])
);
GO
-- adding parent rows
INSERT INTO dbo.Vehicles(ID, [Type])
VALUES(1, 'Car'),
(2, 'Truck');
通过INSERT添加子行没有问题,如下所示:
INSERT INTO dbo.Cars(ID, OtherData)
VALUES(1, 'Some Data');
DELETE FROM dbo.Cars;
令人惊讶的是,MERGE无法添加一个子行:
MERGE dbo.Cars AS TargetTable
USING
( SELECT 1 AS ID ,
'Some Data' AS OtherData
) AS SourceData
ON SourceData.ID = TargetTable.ID
WHEN NOT MATCHED
THEN INSERT (ID, OtherData)
VALUES(SourceData.ID, SourceData.OtherData);
Msg 547, Level 16, State 0, Line 1
The MERGE statement conflicted with the FOREIGN KEY constraint "Cars_FK_Vehicles". The conflict occurred in database "Test", table "dbo.Vehicles".
The statement has been terminated.
这是MERGE中的错误还是我错过了什么?
答案 0 :(得分:10)
对MERGE
来说,这似乎是一个明确的错误。
执行计划具有Clustered Index Merge
运算符,并且应该输出[Cars].ID,[Cars].Type
以对Vehicles
表进行验证。
实验表明,它不是传递值"Car"
作为Type
值,而是传递一个空字符串。通过删除车辆上的检查约束然后插入
INSERT INTO dbo.Vehicles(ID, [Type]) VALUES (3, '');
现在可以使用以下声明
MERGE dbo.Cars AS TargetTable
USING
( SELECT 3 AS ID ,
'Some Data' AS OtherData
) AS SourceData
ON SourceData.ID = TargetTable.ID
WHEN NOT MATCHED
THEN INSERT (ID, OtherData)
VALUES(SourceData.ID, SourceData.OtherData);
但最终结果是它插入了违反FK约束的行。
ID Type OtherData
----------- ----- ----------
3 Car Some Data
ID Type
----------- -----
1 Car
2 Truck
3
之后立即检查约束
DBCC CHECKCONSTRAINTS ('dbo.Cars')
显示违规行
Table Constraint Where
------------- ------------------- ------------------------------
[dbo].[Cars] [Cars_FK_Vehicles] [ID] = '3' AND [Type] = 'Car'
答案 1 :(得分:9)
Merge迭代器似乎输出Type
列的空字符串的原因很有趣。优化程序识别Type
是一个常量值,并应用重写从流中删除该列,稍后将其作为计算标量添加回来。您可以通过向OUTPUT
语句添加MERGE
子句以发出inserted.[Type]
的值来查看此操作。如果没有OUTPUT
子句,则不需要Compute Scalar,因此它会被优化掉,让我们看到原始示例中的平面形状。
在错误出现时合并迭代器(其也可以是一个表合并,而不是聚集索引合并)和计算标量之间的东西需要[Type]
列的值。由于它已从流中删除(尽管在计划中显示为合并的输出),我们最终引用不存在的东西,并产生空字符串。在另一个世界中,SQL Server会以类似空指针违规的方式断言,但这是另一个故事。
这个问题在SQL Server 2012中有点修复,但仍然存在一个错误。我说“有点”固定的,因为这消除了恒定值列关闭重写(所以FK检查得到了真正的价值,寻求,而不是一个空字符串),增加了字符串“车”回来,但在计算标量如果添加OUTPUT inserted.[Type]
,仍然会显示流程。在一个完美的世界中,该计划将简单地输出Merge提供的值,而不是重新计算常量。无论如何,这并不是那么重要(它只是表明实现仍然有点不稳定)但是仍然存在与删除列引用相关的错误:
它只在SQL 2012中使用表变量(所有其他类型的表都可以)重现,但在所有已发布的SQL Server版本上都可以使用任何类型的表对象进行重现:
DECLARE @Bug TABLE
(
id INTEGER PRIMARY KEY,
data AS 'X' PERSISTED,
CHECK (data = 'A')
)
MERGE @Bug AS b USING (VALUES(1)) AS u (id) ON u.id = b.id
WHEN NOT MATCHED THEN INSERT (id) VALUES (u.id)
OUTPUT INSERTED.data;
在点是,CHECK
约束被跳过 - ,检查它被添加到该计划断言操作者,但随后优化掉时优化看到恒定值列和应用其重写。删除Assert允许将值“X”添加到表中,即使它违反了CHECK约束。正如我所说,你可以在2008 R2及更早版本中使用真实表和临时表重现这一点 - 在2012年,它只会漏掉表变量。
保