使用数据库的朋友大家想必都知道,如果想要维持“一堆多”数据表数据的一致性,必须做到以下几点:
1)插入时,外键必须包含于主表(“1”对多中“1”那个表)关联字段集合中。
2)删除时,主表关联字段被删除,其子表关联数据也一律自动被删除。
3)更新时,主表关联字段被更新,其子表关联数据也一律自动被更新。
其实要做到这三点,最简单的一个做法莫过于:
1)打开SQL Management Studio,成功登陆之后打开某个数据库(展开“+”节点),并且找到“Diagram”(关联图),导入所需要的表。
2)从主表(“1”)鼠标左键按住不放,拖拽到从表(“多”)的“外键”字段。在弹出的对话框中分别确定设置主表和从表即将关联的主键和外键。
3)右键生成的那个关联线,设置插入/删除更新时候的情况:
更新和删除规则有若干选项——其中“级联”就是本文开头所说的部分——“一荣俱荣,一损俱损”。
至于“插入”时的外键一致性,在“强制外键约束”默认设定为“是”可以得到体现(因为一旦创建了表关系,那么外键也就自动检查必须是隶属于主表关联字段的一个元素)。
其实我们也可以采用Trigger来完成同样的任务——所谓Trigger即“触发器”(可以理解成“事件”,是一个特定动作行为产生之后所带来的连锁反应)。
对于“插入”时外键约束(我们假设有Categories和Products表,其中Category主表,其Id关联于从表的Products的CategoryId)。Trigger可以这样写:
Create TRIGGER [dbo].[AutoCheck] ON [dbo].[tb_Products] for insert AS BEGIN begin tran --保存欲待插入的categoryId declare @newCategoryId int select @newCategoryId = categoryid from inserted if(select count(*) from tb_Category where id=@newCategoryId)=1 begin print('成功插入!') commit end else begin print('插入失败!') rollback tran end END
for insert其实可以看成是类似C#或者Basic.NET中的Inserting事件一样——我们知道,C#中某些控件(比如ASP.NET中GridView有Updating和Updated等事件,为了拦截插入的数据,我们可以使用该事件;该事件其中有一些参数自动记录欲插入数据表的新数据为我们所用并处理)。自然地,该“参数”在SQL中其实就是inserted表(每次插入记录,都会生成这张临时表,且里边只有一条记录)。我们从里边获取了categoryId并且在Category表中做搜寻——如果找到,说明存在(可以作为外键插入,否则不行)。因此还用了if……else进行判断。为了防止错误插入保持数据一致性,我们必须用“事物”(begin transcation……commit……rollback)代码块把SQL代码封装起来。
与此类似地还有delete和update——这里只给出delete的级联删除(使用Trigger完成)的示例代码:
Create TRIGGER [dbo].[CascadingDelete] ON [dbo].[tb_Category] for DELETE AS BEGIN begin transaction declare @primaryKey int select @primaryKey = id from deleted delete from tb_Products where CategoryId=@primaryKey commit END
总而言之,SQL实现的级联添加/删除或者更新其本质就是类似一个内部的触发器原理。
既然“级联”触发器完全可以被SQL内置的“关系图”取代,为何还是需要它呢?原因很简单——在某些情况下SQL是无法使用“级联删除/更新”滴。
假设有三张表Category,Products和Order。现在Category<==>order和Product<==>Order分别构成1对多关系(级联删除/更新),且Category和Products也构成一对多(级联删除/更新),此时如果使用“关系图”拉关系设置级联,将会触发“多重循环级联……”的问题,原因很简单,如果可以的话,那么删除一个Category将附带着删除对应的order内容;同时删除products内容;但是product又和order形成了级联删除关系,那么当删除一个category,级联删除products的同时,order也会被级联删除,等于删除了二次,数据会造成错误的。
一般设置表级联关系时,不允许形成这种环形级联。
但是如果你硬要这样做,必须破坏“环”——有两种做法:
1)删除Category和Orders的级联关系(推荐)。
2)删除Product和Orders级联关系,改用触发器在Products上实现(不推荐,因为Category没有必要直接控制Orders,一般删除Category必然删除Products,而删除products才导致order删除,因此第一种方案最好)。