给定两个表TableA(id:主键,类型:tinyint,...)和TableB(id:主键,tableAId:TableA.id上的外键,...)
TableA.type上有一个CHECK CONSTRAINT,允许值为(0,1,2,3),其他值都被禁止。
由于已知的限制,TableB中的记录只有在TableB.TableAId引用TableA中的记录时才能存在TableA.type = 0,1或2而不是3.后一种情况被禁止并导致系统无效状态。
我怎样才能保证在这种情况下INSERT到TableB会失败?
答案 0 :(得分:4)
使用空索引视图的跨表约束:
CREATE TABLE dbo.TableA
(
id integer NOT NULL PRIMARY KEY,
[type] tinyint NOT NULL
CHECK ([type] IN (0, 1, 2, 3))
);
CREATE TABLE dbo.TableB
(
id integer NOT NULL PRIMARY KEY,
tableAId integer NOT NULL
FOREIGN KEY
REFERENCES dbo.TableA
);
-- This view is always empty (limited to error rows)
CREATE VIEW dbo.TableATableBConstraint
WITH SCHEMABINDING AS
SELECT
Error =
CASE
-- Error condition: type = 3 and rows join
WHEN TA.[type] = 3 AND TB.id = TA.id
-- For a more informative error
THEN CONVERT(bit, 'TableB cannot reference type 3 rows in TableA.')
ELSE NULL
END
FROM dbo.TableA AS TA
JOIN dbo.TableB AS TB
ON TB.id = TA.id
WHERE
TA.[type] = 3;
GO
CREATE UNIQUE CLUSTERED INDEX cuq
ON dbo.TableATableBConstraint (Error);
-- All succeed
INSERT dbo.TableA (id, [type]) VALUES (1, 1);
INSERT dbo.TableA (id, [type]) VALUES (2, 2);
INSERT dbo.TableA (id, [type]) VALUES (3, 3);
INSERT dbo.TableB
(id, tableAId)
VALUES
(1, 1),
(2, 2);
-- Fails
INSERT dbo.TableB (id, tableAId) VALUES (3, 3);
-- Fails
UPDATE dbo.TableA SET [type] = 3 WHERE id = 1;
这在概念上与 linked answer 到 Check constraints that ensures the values in a column of tableA is less the values in a column of tableB 类似,但此解决方案是独立的(不需要始终包含多行的单独表)。它还会产生更多信息性错误消息,例如:
<块引用>消息 245,级别 16,状态 1
转换 varchar 值“TableB 无法引用 TableA 中的类型 3 行”时转换失败。到数据类型位。
错误条件必须在 CASE
表达式中完全指定,以确保在所有情况下都能正确操作。不要试图省略语句其余部分所隐含的条件。在此示例中,省略 TB.id = TA.id
(由连接暗示)将是错误的。
SQL Server 查询优化器可以自由地重新排序谓词,并且不对标量表达式的计算时间或计算次数做出一般保证。特别是,scalar computations can be deferred。
完全在 CASE
表达式中指定错误条件可确保一起评估完整的测试集,并且不会早于正确性要求。从执行计划的角度来看,这意味着与 CASE
测试关联的计算标量将出现在索引视图增量维护分支上:
浅色阴影区域突出显示索引视图维护区域;包含 CASE
表达式的计算标量是深色阴影。