【问题标题】:How to do these three things in a SQL Server transaction - 1. create table, 2.create trigger on table, 3. select from another table如何在 SQL Server 事务中执行这三件事 - 1. 创建表,2. 在表上创建触发器,3. 从另一个表中选择
【发布时间】:2021-08-25 19:14:11
【问题描述】:

我正在尝试以事务的形式完成以下 3 个简单任务(即,我需要锁定 old_table 和 new_table 直到该过程完成)。

  1. 创建新表 (new_table)
  2. 向 old_table 添加一个触发器,它将对 new_table 的更新排队。
  3. 从old_table中选择所有数据并返回。

请注意,我希望在单个事务中处理这些。我不能允许在触发器创建和 old_table 上的选择之间插入 old_table(因此触发插入 new_table)。

我目前最接近的尝试是这样,但说实话,我觉得我离用这段代码实现目标还很远。我添加了代码只是为了参考我正在尝试的内容,但我最感兴趣的是非特定答案,这些答案布局如何在事务中完成上述三个命令。

DROP PROCEDURE IF EXISTS dbo.BuildAll;

CREATE PROCEDURE dbo.BuildAll
AS
BEGIN
    BEGIN TRANSACTION
    DECLARE @TriggerCode VARCHAR(MAX)
   
    CREATE TABLE dbo.new_table
    (
        status nvarchar(5),
        type   char(1),
        col1   nvarchar(50),
        col2   smallint
    )

    SELECT @TriggerCode = 'CREATE TRIGGER myTrigger
                           ON dbo.old_table FOR INSERT  
                           AS
                              DECLARE @col1_new nvarchar(50)
                              DECLARE @col2_new  smallint 
    
                              SELECT @col1_new = col1 FROM inserted
                              SELECT @col2_new = col2 FROM inserted
        
                              IF @col1_new IS NOT NULL
                              BEGIN
                                  INSERT INTO new_table (status, type, col1, col2)
                                  SELECt "Q", "A", @col1, @col2 FROM inserted
                              END'
       
    EXEC(@TriggerCode)
  
    SELECT * FROM old_table

    COMMIT
END

【问题讨论】:

  • 您的触发器有 MAJOR 缺陷,您似乎认为它会被 每行调用一次 - 事实并非如此。触发器将每条语句触发一次,因此如果您的INSERT 导致此触发器触发插入25 行,您将触发触发器一次 和@987654324 @ 伪表将包含 25 行。您的代码将从Inserted 中选择这 25 行中的哪一行?它是不确定的,您将获得任意行,而您将忽略所有其他行。您需要重写触发器以考虑到这一点!
  • 老实说,这看起来像是一个问题的解决方案,可能会以更好的方式解决,可能使用您还不知道的功能。如果您尝试生成一些时间稳定的数据快照,请考虑临时表或快照隔离;如果您想原子地切换表,请查看ALTER TABLE SWITCH 或在事务中执行sp_rename;如果您需要复制数据,请考虑事务复制。我想不出任何需要或需要使用触发器的上述方法的情况。
  • 这看起来很奇怪,可能是an XY problem。概括说明您正在尝试做什么。
  • @Stu 这不是 XY 问题。对于问题陈述,我进行了编辑以增加清晰度。
  • @DaleK 我很抱歉。我怀疑我在您进行更改的同时进行了编辑。我的头现在有点凉了,我已经简化了这个问题。我希望这会有所帮助。

标签: sql sql-server tsql triggers


【解决方案1】:

建议这是一个您可以尝试的可能解决方案。这并不能解决您实际触发器的正确​​性,您真的有两个不同的问题。

您无需将整个过程封装在事务中。

  • 创建您的新表。

  • 在旧表上创建触发器,但已禁用。

set transaction isolation level serializable
begin tran
go
create trigger <Name> on <Table> etc
go
disable trigger <Name> on <Table>
go
commit
  • 现在在事务中,您可以在工作时锁定旧表以防止其他活动

begin tran

update oldtable with(tablockx) set column=column where id=0 /* block other processes from updating table, id=0 row doesn't exist */

query your data and process as required

enable trigger <Name> on <Table>

commit

【讨论】:

  • 当我从 dbeaver 运行它时,这个策略很好,但是当我尝试从 C# 将上述内容作为字符串传递时,我仍然收到以下错误:“CREATE TRIGGER”必须是查询批处理中的第一个语句。”话虽这么说,您的解决方案效果很好,我认为它应该被标记为正确,对于那些期待未来的人。
【解决方案2】:

你的这个触发器代码有点奇怪......你在所有三个操作上都有一个触发器 - 但它看起来好像你从来没有使用你从 deleted 伪表中获取的值,如果值从inserted 表中是NULL,你没有在你的触发器内做任何事情——所以你真的可以省去DELETE 的情况——那永远不会做任何事情......

另外,正如我在评论中提到的那样——Inserted 伪表可以很容易地包含 多行——但你从中选择,就好像你只期望它包含单行一样。

你应该真的重写你的触发代码来处理Inserted中多行的情况,并使整个事情正确地基于集合 - 像这样:

CREATE TRIGGER myTrigger
ON dbo.old_table 
FOR INSERT, UPDATE
AS
    INSERT INTO new_table (status, type, col1, col2)
        SELECT 'Q', 'A', i.col1, i.col2
        FROM Inserted i

UPDATE 案例中你是否需要这个 - 我不能说,你需要决定这个。但基本上:只需从Inserted 表中选择,获取Col1Col2 值,并将常量值'Q''A' 添加到您的插入以正确处理多行。应该这样做。

【讨论】:

  • 已根据您的评论删除了未使用的值,但留下了我实际上将使用所有三个场景的逻辑(插入、更新、删除),但为简单起见,我现在只在上面的代码。我正在努力解决的是为什么我无法构建一个事务,其中包括使用表创建和选择语句创建触发器。这就是我遇到麻烦的地方。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-04-03
  • 2018-11-02
  • 2017-07-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多