Mat*_*ieu 5 trigger foreign-key sql-server delete cascade
我的代码在 PostgreSQL 中运行良好,现在必须将其移植到 MS SQL Server。它涉及具有删除/更新事件潜在周期的表,并且 SQL Server 正在抱怨它:
-- TABLE t_parent
CREATE TABLE t_parent (m_id INT IDENTITY PRIMARY KEY NOT NULL, m_name nvarchar(450));
-- TABLE t_child
CREATE TABLE t_child (m_id INT IDENTITY PRIMARY KEY NOT NULL, m_name nvarchar(450),
id_parent int CONSTRAINT fk_t_child_parent FOREIGN KEY REFERENCES t_parent(m_id)
--ON DELETE CASCADE ON UPDATE CASCADE
);
-- TABLE t_link
CREATE TABLE t_link (m_id INT IDENTITY PRIMARY KEY NOT NULL,
id_parent int CONSTRAINT fk_t_link_parent FOREIGN KEY REFERENCES t_parent(m_id)
-- ON DELETE CASCADE ON UPDATE CASCADE
, id_child int CONSTRAINT fk_t_link_child FOREIGN KEY REFERENCES t_child(m_id)
-- ON DELETE SET NULL ON UPDATE CASCADE
, link_name nvarchar(450));
Run Code Online (Sandbox Code Playgroud)
我已经注释掉了ON DELETE/UPDATE
PostgreSQL 接受的约束,它显示了我试图在 MS SQL Server 中重现的确切行为,否则我会收到错误:
在表 't_link' 上引入 FOREIGN KEY 约束 'fk_t_link_child' 可能会导致循环或多个级联路径。指定 ON DELETE NO ACTION 或 ON UPDATE NO ACTION,或修改其他 FOREIGN KEY 约束。
因此,我删除了它们(相当于NO ACTION
从文档中删除)并决定在删除相关行时采用触发方式(如多个站点所暗示的那样)删除相关t_link
行t_parent
:
CREATE TRIGGER trg_delete_CASCADE_t_link_id_parent ON t_parent AFTER DELETE AS BEGIN
DELETE FROM t_link WHERE id_parent IN (SELECT m_id FROM DELETED)
END;
Run Code Online (Sandbox Code Playgroud)
我总体上想要的是:
t_child
当相关t_parent
记录被删除时,所有记录被删除(ON DELETE CASCADE
),以及t_link
与删除相关的记录也被t_child
删除t_link
删除相关t_parent
记录时删除所有记录( ON DELETE CASCADE
)t_link.id_child
设置为何NULL
时t_child
删除或删除他们的相关记录 ,如果它使事情变得更容易(ON DELETE SET NULL
或ON DELETE CASCADE
)然后我插入一些测试数据并尝试:
insert into t_parent (m_name) values('toto');
insert into t_link (id_parent, id_child, link_name) values (1, NULL, 'chan');
delete from t_parent where m_id = 1;
Run Code Online (Sandbox Code Playgroud)
错误:DELETE 语句与 REFERENCE 约束“fk_t_link_parent”冲突。冲突发生在数据库“DBTest”、表“dbo.t_link”、“id_parent”列中。
我猜问题是我的触发器没有被调用,因为它发生在删除本身之后,并因上述消息而失败;并且没有BEFORE DELETE
触发类型(这听起来像是我想要的)。
现在我不得不说 SQL 都是由一个类似 Java JPA 的程序生成的,该程序必须处理不同的 DBMS(PostgreSQL 的一个子类,SQL Server 的一个子类,......)所以我应该保持通用:我可以' t 将ON DELETE CASCADE
约束放在一个表上并与其他人一起使用触发器(或您可能知道的任何其他方法)(我可以,但代价是我试图避免的代码过度复杂化)。
SQL Server 是一个Docker 映像,所以我不确定我是否可以在某处(除非在sqlcmd
命令中)有调试输出。如果有任何相关性,版本是 2017。
我看到的唯一解决方法就是删除引用约束并使用触发器手动处理它。但是:有外键约束有什么意义呢?
编辑:在大卫的回答之后,我应该澄清几点:
的CREATE TABLE
和CREATE TRIGGER
SQL由代码生成,每次一个新的表将被添加(从SQL无关的配置文件)。由于 SQL Server 可能ON DELETE CASCADE
由于潜在的循环而拒绝创建约束,因此我决定只指明FOREIGN KEY
约束,然后让每个引用的表t_parent
创建一个FOR DELETE
触发器,每个表对自己的行执行 CASCADE 或 SET NULL 操作。
建议的INSTEAD OF DELETE
触发器绝对是我正在寻找的机制,但只能为表创建此类触发器的单个实例(这是有道理的),所以我没有那样做。
我可能最终会创建存储过程而不是我当前的触发器,并在每次添加新引用表(和过程)时更新 INSTEAD OF 触发器,调用每个存储过程。
你很接近了。 AFTER
触发器在外键约束检查之后发生。所以你需要一个INSTEAD OF
触发器。这样您就可以在对目标表执行 DELETE 之前修改子表。
例如
-- TABLE t_parent
CREATE TABLE t_parent
(
m_id INT IDENTITY PRIMARY KEY NOT NULL,
m_name nvarchar(450)
);
-- TABLE t_child
CREATE TABLE t_child
(
m_id INT IDENTITY PRIMARY KEY NOT NULL,
m_name nvarchar(450),
id_parent int CONSTRAINT fk_t_child_parent FOREIGN KEY REFERENCES t_parent(m_id)
ON DELETE CASCADE ON UPDATE CASCADE
);
-- TABLE t_link
CREATE TABLE t_link (m_id INT IDENTITY PRIMARY KEY NOT NULL,
id_parent int CONSTRAINT fk_t_link_parent FOREIGN KEY REFERENCES t_parent(m_id)
ON DELETE NO ACTION
, id_child int CONSTRAINT fk_t_link_child FOREIGN KEY REFERENCES t_child(m_id)
ON DELETE CASCADE
, link_name nvarchar(450));
go
CREATE OR ALTER TRIGGER trg_delete_CASCADE_t_link_id_parent
ON t_parent INSTEAD OF DELETE
AS
BEGIN
SET NOCOUNT ON
DELETE FROM t_link WHERE id_parent IN (SELECT m_id FROM DELETED);
DELETE FROM t_parent WHERE m_id IN (SELECT m_id FROM DELETED);
END;
go
insert into t_parent (m_name) values('toto');
insert into t_link (id_parent, id_child, link_name) values (1, NULL, 'chan');
delete from t_parent where m_id = 1;
Run Code Online (Sandbox Code Playgroud)
这样 t_parent->t_child->t_link 使用 CASCADE DELETES,而 t_parent->t_link 由 INSTEAD OF 触发器处理。
归档时间: |
|
查看次数: |
3351 次 |
最近记录: |