sql-server - 如何在 T-SQL/SQL Server 中模拟超/子类型(继承)实体的 BEFORE INSERT 触发器?

标签 sql-server tsql triggers azure-sql-database

这是在 Azure 上。

我有一个父类(super class)型实体和几个子类型实体,后者需要在每次插入时从父类(super class)型实体的主键获取其外键。在 Oracle 中,我使用 BEFORE INSERT触发来完成此操作。如何在 SQL Server/T-SQL 中实现这一目标?

DDL

CREATE TABLE super (
 super_id int IDENTITY(1,1)
 ,subtype_discriminator char(4) CHECK (subtype_discriminator IN ('SUB1', 'SUB2')
 ,CONSTRAINT super_id_pk PRIMARY KEY (super_id)
);
CREATE TABLE sub1 (
 sub_id int IDENTITY(1,1)
,super_id int NOT NULL
,CONSTRAINT sub_id_pk PRIMARY KEY (sub_id)
,CONSTRAINT sub_super_id_fk FOREIGN KEY (super_id) REFERENCES super (super_id)
);

我希望插入sub1触发一个触发器,该触发器实际上将一个值插入 super并使用 super_id生成并放入 sub1 .

在 Oracle 中,这可以通过以下方式完成:

CREATE TRIGGER sub_trg
    BEFORE INSERT ON sub1
    FOR EACH ROW
DECLARE
    v_super_id int; //Ignore the fact that I could have used super_id_seq.CURRVAL
BEGIN
    INSERT INTO super (super_id, subtype_discriminator) 
        VALUES (super_id_seq.NEXTVAL, 'SUB1') 
        RETURNING super_id INTO v_super_id;
    :NEW.super_id := v_super_id;
END;

鉴于 T-SQL 缺少 BEFORE INSERT,请告知我如何在 T-SQL 中模拟这一点。能力?

最佳答案

有时是BEFORE触发器可以替换为 AFTER一,但在您的情况下似乎并非如此,因为您显然需要在插入发生之前提供一个值。因此,为此目的,最接近的功能似乎是 INSTEAD OF触发一,如 @marc_s has suggested在他的评论中。

但请注意,正如这两种触发器类型的名称所示,BEFORE 之间存在根本区别。触发器和 INSTEAD OF一。在这两种情况下,触发器都是在调用触发器的语句确定的操作尚未发生时执行的,在 INSTEAD OF 的情况下。触发该操作根本不应该发生。您需要完成的实际操作必须由触发器本身来完成。这与 BEFORE 非常不同触发器功能,其中语句始终要执行,当然,除非您显式地将其回滚。

但实际上还有一个问题需要解决。正如您的 Oracle 脚本所示,您需要转换的触发器使用 SQL Server 不支持的另一个功能,即 FOR EACH ROW 的功能。 。 SQL Server 中也没有每行触发器,只有每语句触发器。这意味着您需要始终牢记插入的数据是行集合,而不仅仅是一行。这增加了更多的复杂性,尽管这可能会结束您需要考虑的事项列表。

所以,实际上有两件事需要解决:

  • 替换 BEFORE功能;

  • 替换 FOR EACH ROW功能。

我解决这些问题的尝试如下:

CREATE TRIGGER sub_trg
ON sub1
INSTEAD OF INSERT
AS
BEGIN
  DECLARE @new_super TABLE (
    super_id int
  );
  INSERT INTO super (subtype_discriminator)
  OUTPUT INSERTED.super_id INTO @new_super (super_id)
  SELECT 'SUB1' FROM INSERTED;

  INSERT INTO sub (super_id)
  SELECT super_id FROM @new_super;
END;

这就是上面的工作原理:

  1. 与插入 sub1 中的行数相同首先添加到super 。生成的super_id值存储在临时存储中(名为 @new_super 的表变量)。

  2. 新插入的super_id s 现在插入 sub1 .

其实没什么太难的,但只有当 sub1 中没有其他列时,上述内容才有效。比您在问题中指定的那些。如果还有其他列,上面的触发器将需要更复杂一些。

问题是分配新的 super_id分别到每个插入的行。实现映射的一种方法如下所示:

CREATE TRIGGER sub_trg
ON sub1
INSTEAD OF INSERT
AS
BEGIN
  DECLARE @new_super TABLE (
    <b>rownum   int IDENTITY (1, 1),</b>
    super_id int
  );
  INSERT INTO super (subtype_discriminator)
  OUTPUT INSERTED.super_id INTO @new_super (super_id)
  SELECT 'SUB1' FROM INSERTED;

  WITH enumerated AS (
    SELECT *, ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS rownum
    FROM inserted
  )
  INSERT INTO sub1 (super_id, <i>other columns</i>)
  SELECT n.super_id, i.<i>other columns</i>
  FROM enumerated AS i
  INNER JOIN @new_super AS n
  ON i.rownum = n.rownum;
END;

如您所见,IDENTIY(1,1)列添加到 @new_user ,所以暂时插入super_id值将从 1 开始额外枚举。提供新 super_id 之间的映射。 s 和新数据行 ROW_NUMBER函数用于枚举INSERTED行也是如此。结果, INSERTED 中的每一行现在可以将集合链接到单个 super_id从而补充了要插入 sub1 的完整数据行.

注意新的super_id的顺序插入的顺序可能与分配的顺序不匹配。我认为这不是问题。全新super生成的行除了 ID 之外都是相同的。因此,您所需要的只是获取一个新的 super_id每新sub1行。

但是,如果插入 super 的逻辑更复杂,由于某种原因,您需要准确记住哪个新 super_id已生成新的 sub行,您可能需要考虑此 Stack Overflow 问题中讨论的映射方法:

关于sql-server - 如何在 T-SQL/SQL Server 中模拟超/子类型(继承)实体的 BEFORE INSERT 触发器?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15458006/

相关文章:

SQL 按日期获取前 10 条记录

sql - 投影内相关子查询排序的影响

sql-server - 将一列中的值拆分为两列

sql - 对具有特定值的多列的约束

mysql - 选择前 3 个最多计数组 - SQL

sql - 将 XML 插入 SQL Server 表

sql - 触发检查重复项

mysql - 创建MYSQL触发器语句时错误代码:1064

sql - 在 SQLite 中创建条件 SQL 触发器

sql-server - 将数据加载到Hive中时面临的问题