SQL Server BEFORE UPDATE触发器,它在执行更新之前在字段上添加时间戳
嗨,斯塔克沃夫的好人们 我正在为SQLServer2008R2上的一个表编写一个触发器,用于审计目的,它应该在发送更新查询以执行之前为UPDATE_TS字段添加一个时间戳。结果是,在查询时要更新的原始值加上触发器设置的更新的附加值时发生更新 我也对这个问题进行了很好的编辑,因为我听说与不使用内部联接相比,内部联接在触发器上的性能不是很高。我不确定这是否会增加触发器的额外开销,而不是避免触发器中的内部联接 下面是我正在研究的示例。谢谢你的帮助和建议 示例表称为MY_表:SQL Server BEFORE UPDATE触发器,它在执行更新之前在字段上添加时间戳,sql,sql-server,sql-server-2008,tsql,Sql,Sql Server,Sql Server 2008,Tsql,嗨,斯塔克沃夫的好人们 我正在为SQLServer2008R2上的一个表编写一个触发器,用于审计目的,它应该在发送更新查询以执行之前为UPDATE_TS字段添加一个时间戳。结果是,在查询时要更新的原始值加上触发器设置的更新的附加值时发生更新 我也对这个问题进行了很好的编辑,因为我听说与不使用内部联接相比,内部联接在触发器上的性能不是很高。我不确定这是否会增加触发器的额外开销,而不是避免触发器中的内部联接 下面是我正在研究的示例。谢谢你的帮助和建议 示例表称为MY_表: CREATE TABLE
CREATE TABLE [myschema].[MY_TABLE](
[MY_TABLE_ID] [bigint] IDENTITY(1,1) NOT NULL,
[FIELD_TO_UPDATE] [varchar](255) NOT NULL,
[CREATE_TS] [datetime] NULL,
[UPDATE_TS] [datetime] NULL),
PRIMARY KEY (MY_TABLE_ID))
要创建的触发器:
CREATE TRIGGER [myschema].[my_table_update_ts_trigger] ON [mydb].[myschema].[MY_TABLE]
INSTEAD OF UPDATE
AS
BEGIN
UPDATE INTO MY_TABLE ([FIELD_TO_UPDATE],[UPDATE_TS])
SELECT ins.FIELD_TO_UPDATE, GETDATE() FROM INSERTED as ins
END
您需要标识需要更新的行,并使用联接或半联接来完成此操作。它不会比这更有效,除非您根本不执行更新:
CREATE TRIGGER [myschema].[my_table_update_ts_trigger]
ON [myschema].[MY_TABLE]
INSTEAD OF UPDATE
AS
BEGIN
UPDATE t SET
FIELD_TO_UPDATE = i.FIELD_TO_UPDATE,
UPDATE_TS = CURRENT_TIMESTAMP
FROM myschema.MY_TABLE AS t
INNER JOIN inserted AS i
ON t.MY_TABLE_ID = i.MY_TABLE_ID;
END
GO
以下是执行计划:
由于您需要将插入的中的行与基表匹配,并且由于任何操作都可能会更新多行(在SQL Server中触发每条语句,而不像在其他一些平台中那样触发每行),并且由于这不是更新前而是更新而非更新(这意味着您仍然必须实际执行在没有触发器的情况下可能发生的更新),您需要两个表的输出才能准确执行更新。这意味着您需要连接,并且不能使用半连接(例如,EXISTS),这可能仍然违反了您的古怪要求。如果您只需要更新时间戳,则可以执行以下操作:
UPDATE t SET UPDATE_TS = CURRENT_TIMESTAMP
FROM myschema.MY_TABLE AS t
WHERE EXISTS (SELECT 1 FROM inserted WHERE MY_TABLE_ID = t.MY_TABLE_ID);
不幸的是,这不起作用,因为FIELD\u TO\u UPDATE
会丢失,而不会在正确的联接中实际拉入inserted
伪表
另一种方法是使用交叉应用,例如:
UPDATE t SET
FIELD_TO_UPDATE = i.FIELD_TO_UPDATE,
UPDATE_TS = CURRENT_TIMESTAMP
FROM inserted AS i
CROSS APPLY myschema.MY_TABLE AS t
WHERE i.MY_TABLE_ID = t.MY_TABLE_ID;
它也缺少讨厌的JOIN关键字,但仍在执行JOIN。您可以看到这一点,因为执行计划是相同的:
现在,从理论上讲,您可以在没有连接的情况下执行此操作,但这并不意味着它的性能会更好。事实上,我毫无疑问地向您保证,这将降低效率,即使它不包含一个单独的四个字母的单词,如连接:
DECLARE @NOW DATETIME = CURRENT_TIMESTAMP,
@MY_TABLE_ID INT,
@FIELD_TO_UPDATE VARCHAR(255);
DECLARE c CURSOR LOCAL FAST_FORWARD FOR
SELECT MY_TABLE_ID, FIELD_TO_UPDATE FROM inserted;
OPEN c;
FETCH NEXT FROM c INTO @FIELD_TO_UPDATE, @MY_TABLE_ID;
WHILE @@FETCH_STATUS = 0
BEGIN
UPDATE myschema.MY_TABLE SET
FIELD_TO_UPDATE = @FIELD_TO_UPDATE,
UPDATE_TS = @NOW
WHERE MY_TABLE_ID = @MY_TABLE_ID;
FETCH NEXT FROM c INTO @FIELD_TO_UPDATE, @MY_TABLE_ID;
END
CLOSE c;
DEALLOCATE c;
也就是说,如果你认为这个解决方案会比有连接的解决方案快一点,我可以把佛罗里达州的一些沼泽地卖给你。这里也有多座桥。我甚至不想费心展示这个解决方案的执行计划
让我们也比较一下INSERT而非INSERT触发器中发生的情况。下面是一个示例,可能与您的示例类似:
CREATE TRIGGER myschema.ins_my_table
ON myschema.MY_TABLE
INSTEAD OF INSERT
AS
INSERT myschema.MY_TABLE(FIELD_TO_UPDATE, CREATE_TS)
SELECT FIELD_TO_UPDATE, CURRENT_TIMESTAMP FROM inserted;
GO
这也将生成一个计划,看起来执行了两个查询:
需要注意的是,INSTEAD OF触发器会取消原始更新,并且您负责发布自己的更新(即使计划仍显示两个查询)
最后一个选项是使用AFTER触发器,而不是instead of触发器。这将允许您更新时间戳,而不使用联接,因为字段_to_update已经更新。但在这种情况下,您将看到两个查询,并且将真正执行两个查询(不仅仅是在计划中这样看)
一些一般性评论
因为我要提高性能,所以我不想在用于触发器的代码中使用任何内部联接
这没有多大意义;为什么您认为连接对性能有害?听起来您已经看了太多NoSQL视频。请不要因为听说技术不好或连接速度慢而放弃技术。创建有意义的查询,在性能不好时进行优化,在无法选择时寻求帮助imize。在几乎所有情况下(当然也有例外),问题在于索引或统计,而不是您使用了联接关键字。这并不意味着您应该不惜一切代价避免所有查询中的所有联接。您需要标识行您需要更新,您可以使用连接或半连接来完成此操作。除非您根本不执行更新,否则不会比这更有效:
CREATE TRIGGER [myschema].[my_table_update_ts_trigger]
ON [myschema].[MY_TABLE]
INSTEAD OF UPDATE
AS
BEGIN
UPDATE t SET
FIELD_TO_UPDATE = i.FIELD_TO_UPDATE,
UPDATE_TS = CURRENT_TIMESTAMP
FROM myschema.MY_TABLE AS t
INNER JOIN inserted AS i
ON t.MY_TABLE_ID = i.MY_TABLE_ID;
END
GO
以下是执行计划:
由于您需要将插入的中的行与基表匹配,并且由于任何操作都可能会更新多行(在SQL Server中触发每条语句,而不像在其他一些平台中那样触发每行),并且由于这不是更新前而是更新而非更新(这意味着您仍然必须实际执行在没有触发器的情况下可能发生的更新),您需要两个表的输出才能准确执行更新。这意味着您需要连接,并且不能使用半连接(例如,EXISTS),这可能仍然违反了您的古怪要求。如果您只需要更新时间戳,则可以执行以下操作:
UPDATE t SET UPDATE_TS = CURRENT_TIMESTAMP
FROM myschema.MY_TABLE AS t
WHERE EXISTS (SELECT 1 FROM inserted WHERE MY_TABLE_ID = t.MY_TABLE_ID);
不幸的是,这不起作用,因为FIELD\u TO\u UPDATE
会丢失,而不会在正确的联接中实际拉入inserted
伪表
另一种方法是使用交叉应用,例如:
UPDATE t SET
FIELD_TO_UPDATE = i.FIELD_TO_UPDATE,
UPDATE_TS = CURRENT_TIMESTAMP
FROM inserted AS i
CROSS APPLY myschema.MY_TABLE AS t
WHERE i.MY_TABLE_ID = t.MY_TABLE_ID;
它也缺少讨厌的JOIN关键字,但仍在执行JOIN。您可以看到这一点,因为执行计划是相同的:
现在,从理论上讲,您可以在没有连接的情况下执行此操作,但这并不意味着它的性能会更好。事实上,我毫无疑问地向您保证,这将降低效率,即使它不包含一个单独的四个字母的单词,如连接:
DECLARE @NOW DATETIME = CURRENT_TIMESTAMP,
@MY_TABLE_ID INT,
@FIELD_TO_UPDATE VARCHAR(255);
DECLARE c CURSOR LOCAL FAST_FORWARD FOR
SELECT MY_TABLE_ID, FIELD_TO_UPDATE FROM inserted;
OPEN c;
FETCH NEXT FROM c INTO @FIELD_TO_UPDATE, @MY_TABLE_ID;
WHILE @@FETCH_STATUS = 0
BEGIN
UPDATE myschema.MY_TABLE SET
FIELD_TO_UPDATE = @FIELD_TO_UPDATE,
UPDATE_TS = @NOW
WHERE MY_TABLE_ID = @MY_TABLE_ID;
FETCH NEXT FROM c INTO @FIELD_TO_UPDATE, @MY_TABLE_ID;
END
CLOSE c;
DEALLOCATE c;
也就是说,如果你认为这个解决方案会比有连接的方案快一点的话,我可以在佛罗里达州的一些沼泽地