SQL Server:如何强制主键不能作为主键存在于另一个表约束 - 触发器中

时间:2014-01-31 02:48:43

标签: sql-server triggers primary-key

我的表格LotTablePK= LotID, Name, rate

我有另一张LotTranslatePK=TranslateLotID

的表格FK=MasterLotID

在插入LotTable之前,我需要确保强制插入的PK不是LotTranslate中的PK。

我的问题是我做了一个触发器而不是插入或删除后?什么是最干净的方式,快速检查这个表并在LotTable中停止插入,如果在LotTranslate中找到PK?

我的方向我不确定这是否是正确的SQL Server方式......

CREATE TRIGGER tr_LotsInsert ON LotTable
INSTEAD OF INSERT
AS
BEGIN
SET NOCOUNT ON
    INSERT INTO dbo.LotTable
    SELECT *
    FROM INSERTED
    WHERE INSERTED.LotID not in (select TranslateLotID from LotTranslate)
END

1 个答案:

答案 0 :(得分:1)

我不建议使用触发器强制执行此操作。

您所描述的实际上是继承,其中不同的对象共享基本类型。在这种情况下,您具有Lot(称为超类型)的基本概念,以及两个互斥的子类型LotTable和{{ 1}}。 (并且为了记录,我认为很遗憾您的数据库中有一个名为LotTranslate的表,除非它实际上处理某些不是数据库对象的表。)

有一个相当完善的数据库设计模式来处理子类型和超类型:创建一个父表,在继承模式中用作“基础对象”,并使子类型表都与它有FK关系。要进一步强制执行互斥,您可以向所有表添加Table列,并将其包含在外键中。

然后,您的基表以1对零或一对的关系参与两个表。到此处最重要的概念是Type在所有表中始终相同,并且您不为任何表创建单独的代理键:base / supertype表包含子/中的相同值子类型表。

在我向您展示如何完成此操作之前,请允许我提一下,在这种情况下,您可以将两个表组合成一个表,并使用简单的LotID列指示哪个表当然会阻止单一批次同时是两种类型。但是,我假设你的两个表之间有足够不同的列,这样做会大大浪费Type值(如果只有几列不同,那么可能更好一些结合它们。)

NULL

请注意,您可能希望完成这项工作,以便将子表中的新CREATE TABLE dbo.LotBase ( LotID int NOT NULL CONSTRAINT PK_LotBase PRIMARY KEY CLUSTERED, LotTypeID tinyint NOT NULL CONSTRAINT FK_LotBase_LotTypeID FOREIGN KEY REFERENCES dbo.LotType (LotTypeID), -- A unique constraint needed for FK purposes CONSTRAINT UQ_LotBase_LotID_LotTypeID UNIQUE (LotID, LotTypeID) ); -- Include script here to create a LotType table and populate it with two rows -- 1 = `Standard Lot` and 2 = `TranslateLot` INSERT dbo.LotBase (LotID, LotTypeID) SELECT LotID, 1 FROM dbo.LotTable; INSERT dbo.LotBase (LotID, LotTypeID) SELECT TranslateLotID, 2 FROM dbo.LotTranslate; ALTER TABLE dbo.LotTable ADD LotTypeID tinyint NOT NULL CONSTRAINT DF_LotTable_LotTypeID DEFAULT (1); ALTER TABLE dbo.LotTranslate ADD LotTypeID tinyint NOT NULL CONSTRAINT DF_LotTranslate_LotTypeID DEFAULT (2); ALTER TABLE dbo.LotTable ADD CONSTRAINT FK_LotTable_LotBase FOREIGN KEY (LotID, LotTypeID) REFERENCES dbo.LotBase (LotID, LotTypeID); ALTER TABLE dbo.LotTable ADD CONSTRAINT FK_LotTable_LotBase FOREIGN KEY (LotID, LotTypeID) REFERENCES dbo.LotBase (LotID, LotTypeID); 列放在LotTypeID列之后,但这取决于您 - 只需要小心因为它需要表重新创建,如果你不了解并小心,你可能会损害你的数据库(先备份!)。

这种模式的一个巨大好处是不要错过在数据库中你想要FK到LotID的任何地方,你可以选择使用其中一个子表或使用父表。这会限制您的其他表以允许其中一个或仅一个子类型。不容错过的另一个好处是,您可以将两个表之间的公共列放入父表中,而不是在子表中重复。最后,您可以为每个子项创建一个视图,该子视图公开组合的父+子列,就像原始子表一样。

最后,如果您继续使用触发器方法,则不必使用Lot触发器。您只能INSTEAD OF任何不合适的交易:

ROLLBACK

这是一种更好的处理方式(一方面,每次在CREATE TRIGGER TR_LotTable_I ON dbo.LotTable FOR INSERT AS SET NOCOUNT ON; SET XACT_ABORT ON; IF EXISTS ( SELECT * FROM Inserted I INNER JOIN dbo.LotTranslate LT ON I.LotID = LT.TranslateLotID ) ROLLBACK TRAN; 表中添加一列时都不需要修改它。另外,我建议您学会使用(然后始终使用{} {}}语法而不是您显示的LotTable语法。虽然我根据我的经验提出了一些争议,但根据我的经验,使用JOIN代替{ {1}}错过了一些关键的概念性学习,这些学习是在弄清楚如何将它们变成IN的过程中继续进行的。还有其他一些实际的好处,例如嵌套的IN查询得到了令人憎恶的事实难以理解和维护,同时再添加5个JOIN s并不能在格式化时更难理解查询。