使用 MS SQL Server 上的触发器解决 ON DELETE CASCADE 循环

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/UPDATEPostgreSQL 接受的约束,它显示了我试图在 MS SQL Server 中重现的确切行为,否则我会收到错误:

在表 't_link' 上引入 FOREIGN KEY 约束 'fk_t_link_child' 可能会导致循环或多个级联路径。指定 ON DELETE NO ACTION 或 ON UPDATE NO ACTION,或修改其他 FOREIGN KEY 约束。

因此,我删除了它们(相当于NO ACTION文档中删除)并决定在删除相关行时采用触发方式(如多个站点所暗示的那样)删除相关t_linkt_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设置为何NULLt_child删除删除他们的相关记录 ,如果它使事情变得更容易(ON DELETE SET NULLON 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 TABLECREATE TRIGGERSQL由代码生成,每次一个新的表将被添加(从SQL无关的配置文件)。由于 SQL Server 可能ON DELETE CASCADE由于潜在的循环而拒绝创建约束,因此我决定只指明FOREIGN KEY约束,然后让每个引用的表t_parent创建一个FOR DELETE触发器,每个表对自己的行执行 CASCADE 或 SET NULL 操作。

建议的INSTEAD OF DELETE触发器绝对是我正在寻找的机制,但只能为表创建此类触发器的单个实例(这是有道理的),所以我没有那样做。

我可能最终会创建存储过程而不是我当前的触发器,并在每次添加新引用表(和过程)时更新 INSTEAD OF 触发器,调用每个存储过程。

Dav*_*oft 4

你很接近了。 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 触发器处理。