如何在 T-SQL/SQL Server 中为超/子类型(继承)实体模拟 BEFORE INSERT 触发器?
这是在 Azure 上.
This is on Azure.
我有一个超类型实体和几个子类型实体,后者需要在每次插入时从超类型实体的主键中获取它们的外键.在 Oracle 中,我使用 BEFORE INSERT
触发器来完成此操作.如何在 SQL Server/T-SQL 中实现这一点?
I have a supertype entity and several subtype entities, the latter of which needs to obtain their foreign keys from the primary key of the super type entity on each insert. In Oracle, I use a BEFORE INSERT
trigger to accomplish this. How would one accomplish this in 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
.
I wish for an insert into sub1
to fire a trigger that actually inserts a value into super
and uses the super_id
generated to put into sub1
.
在 Oracle 中,这将通过以下方式完成:
In Oracle, this would be accomplished by the following:
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 中模拟这一点?
Please advise on how I would simulate this in T-SQL, given that T-SQL lacks the BEFORE INSERT
capability?
推荐答案
有时 BEFORE
触发器可以替换为 AFTER
触发器,但这似乎并不适用在您的情况下就是这种情况,因为您显然需要在插入之前提供一个值.因此,为此目的,最接近的功能似乎是 INSTEAD OF
触发器,如 @marc_s 在他的评论中提出了.
Sometimes a BEFORE
trigger can be replaced with an AFTER
one, but this doesn't appear to be the case in your situation, for you clearly need to provide a value before the insert takes place. So, for that purpose, the closest functionality would seem to be the INSTEAD OF
trigger one, as @marc_s has suggested in his comment.
但是请注意,正如这两种触发器类型的名称所暗示的那样,BEFORE
触发器和 INSTEAD OF
触发器之间存在根本区别.虽然在这两种情况下,触发器都是在由调用触发器的语句确定的操作尚未发生时执行的,但在 INSTEAD OF
触发器的情况下,永远不会假设该操作 发生.您需要执行的实际操作必须由触发器本身完成.这与 BEFORE
触发器功能非常不同,后者的语句总是由于执行而导致,当然,除非您明确回滚它.
Note, however, that, as the names of these two trigger types suggest, there's a fundamental difference between a BEFORE
trigger and an INSTEAD OF
one. While in both cases the trigger is executed at the time when the action determined by the statement that's invoked the trigger hasn't taken place, in case of the INSTEAD OF
trigger the action is never supposed to take place at all. The real action that you need to be done must be done by the trigger itself. This is very unlike the BEFORE
trigger functionality, where the statement is always due to execute, unless, of course, you explicitly roll it back.
但实际上还有一个问题需要解决.正如您的 Oracle 脚本所揭示的,您需要转换的触发器使用了 SQL Server 不支持的另一个特性,即 FOR EACH ROW
.SQL Server 中也没有每行触发器,只有每语句触发器.这意味着您需要始终牢记插入的数据是一行set,而不仅仅是一行.这会增加更多的复杂性,尽管这可能会得出您需要考虑的事项清单.
But there's one other issue to address actually. As your Oracle script reveals, the trigger you need to convert uses another feature unsupported by SQL Server, which is that of FOR EACH ROW
. There are no per-row triggers in SQL Server either, only per-statement ones. That means that you need to always keep in mind that the inserted data are a row set, not just a single row. That adds more complexity, although that'll probably conclude the list of things you need to account for.
那么,真正要解决两件事:
So, it's really two things to solve then:
替换
BEFORE
功能;
替换FOR EACH ROW
功能.
我解决这些问题的尝试如下:
My attempt at solving these is below:
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;
这是上面的工作原理:
与插入
sub1
相同的行数首先添加到super
.生成的super_id
值存储在临时存储中(一个名为@new_super
的表变量).
The same number of rows as being inserted into
sub1
is first added tosuper
. The generatedsuper_id
values are stored in a temporary storage (a table variable called@new_super
).
新插入的super_id
现在被插入到sub1
中.
The newly inserted super_id
s are now inserted into sub1
.
真的没有什么太难的,但是只有在 sub1
中没有其他列而不是您在问题中指定的列时,上述内容才有效.如果还有其他列,上面的触发器就需要稍微复杂一些.
Nothing too difficult really, but the above will only work if you have no other columns in sub1
than those you've specified in your question. If there are other columns, the above trigger will need to be a bit more complex.
问题是将新的 super_id
分别分配给每个插入的行.实现映射的一种方法可能如下所示:
The problem is to assign the new super_id
s to every inserted row individually. One way to implement the mapping could be like below:
CREATE TRIGGER sub_trg
ON sub1
INSTEAD OF INSERT
AS
BEGIN
DECLARE @new_super TABLE (
rownum int IDENTITY (1, 1),
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, other columns)
SELECT n.super_id, i.other columns
FROM enumerated AS i
INNER JOIN @new_super AS n
ON i.rownum = n.rownum;
END;
如您所见,@new_user
中添加了一个 IDENTIY(1,1)
列,因此临时插入的 super_id
值将另外从1开始枚举.为了提供新的super_id
s和新数据行之间的映射,ROW_NUMBER
函数用于枚举INSERTED代码> 行也是如此.因此,
INSERTED
集合中的每一行现在都可以链接到单个 super_id
,从而补充为一个完整的数据行以插入到 sub1中代码>.
As you can see, an IDENTIY(1,1)
column is added to @new_user
, so the temporarily inserted super_id
values will additionally be enumerated starting from 1. To provide the mapping between the new super_id
s and the new data rows, the ROW_NUMBER
function is used to enumerate the INSERTED
rows as well. As a result, every row in the INSERTED
set can now be linked to a single super_id
and thus complemented to a full data row to be inserted into sub1
.
请注意,插入新 super_id
的顺序可能与分配它们的顺序不匹配.我认为这不是问题.除了 ID 之外,所有新生成的 super
行都是相同的.因此,您在这里只需要为每个新的 sub1
行添加一个新的 super_id
.
Note that the order in which the new super_id
s are inserted may not match the order in which they are assigned. I considered that a no-issue. All the new super
rows generated are identical save for the IDs. So, all you need here is just to take one new super_id
per new sub1
row.
但是,如果插入 super
的逻辑更复杂,并且出于某种原因,您需要准确记住为哪个新 生成了哪些新
行,您可能需要考虑此 Stack Overflow 问题中讨论的映射方法:super_id
>sub
If, however, the logic of inserting into super
is more complex and for some reason you need to remember precisely which new super_id
has been generated for which new sub
row, you'll probably want to consider the mapping method discussed in this Stack Overflow question:
- 使用merge..output获取 source.id 和 target.id 之间的映射
相关文章