这是MERGE中的错误,无法正确实施FOREIGN KEY吗?

时间:2011-10-14 18:21:10

标签: sql-server sql-server-2008 sql-server-2008-r2

我使用下表来实现子类型,这是一种非常常见的方法:

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中的错误还是我错过了什么?

2 个答案:

答案 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年,它只会漏掉表变量。