【问题标题】:In a trigger, how do I refer to the row that was just inserted?在触发器中,如何引用刚刚插入的行?
【发布时间】:2016-05-10 02:59:30
【问题描述】:

让我解释一下我的情况。我有 3 个使用

生成的表
CREATE TABLE Partners 
( 
    id INT IDENTITY(1,1),
    name NVARCHAR(50) NOT NULL,
    PRIMARY KEY (id)
);

-- snip ... 
CREATE TABLE Questions 
(
    id INT IDENTITY(1,1),
    section_id INT,
    qtext NVARCHAR(300) NOT NULL,
    PRIMARY KEY (id),
    FOREIGN KEY (section_id) REFERENCES Sections(id) ON DELETE CASCADE
);

-- snip ... 
CREATE TABLE Answers 
(
    id INT IDENTITY (1,1),
    question_id INT,
    partner_id INT,
    val DECIMAL DEFAULT 0.0,
    PRIMARY KEY (id),
    FOREIGN KEY (question_id) REFERENCES Questions(id) ON DELETE CASCADE,
    FOREIGN KEY (partner_id) REFERENCES Partners(id) ON DELETE CASCADE
);

我正在尝试设置一个触发器,以便在添加新合作伙伴时,为每个问题生成默认答案。

我尝试创建该触发器是

-- Create trigger so that adding a partner results in default
-- answers for every survey
-- See https://stackoverflow.com/questions/11852782/t-sql-loop-over-query-results
CREATE TRIGGER DefaultAnswers     
    ON  Partners       
    AFTER INSERT     
AS       
BEGIN    
    CREATE TABLE QuestIds (RowNum INT, Id INT);

    INSERT INTO QuestIds (RowNum, Id)
        SELECT DISTINCT ROW_NUMBER() OVER (ORDER BY Id) as RowNum, Id
        FROM TABLE

    DECLARE @id INT
    DECLARE @totalrows INT = (SELECT COUNT(*) FROM QuestIds)
    DECLARE @currentrow INT = 1

    WHILE @currentrow <  @totalrows  
    BEGIN 
        SET @id = (SELECT Id FROM QuestIds WHERE RowNum = @currentrow)

        EXEC AddAnswerWithoutVal @question_id=@id, @partner_id=INSERTED.id

        SET @currentrow = @currentrow +1
    END 
END

错误是

消息 156,级别 15,状态 1,过程 DefaultAnswers,第 313 行
关键字“TABLE”附近的语法不正确。

消息 102,级别 15,状态 1,过程 DefaultAnswers,第 323 行
'.' 附近的语法不正确。

你能帮我找出问题吗? INSERTED,我想,是指刚刚插入表格的行。至于FROM TABLE,那也是我试图从T-SQL loop over query results 中窃取的东西。

编辑:我还有一个问题。当我开始工作时,触发器会引用 成功 插入的行吗?我想确保这是一个原子操作。

【问题讨论】:

  • inserted 是一个表格。您需要使用游标或其他循环构造来将来自inserted 的值用于存储过程。
  • 插入触发器仅在成功插入行后运行。这是第二个问题的答案。

标签: sql sql-server tsql database-design


【解决方案1】:

使用存储过程插入行似乎是个坏主意。也许出于某种原因这是必要的,但将其表达为单个插入会更容易。像这样的:

insert into Answers(question_id, partner_id, val)
    select q.id, i.id, NULL
    from inserted i cross join
         questions q;

这将替换整个主体,并且不需要临时表。在触发器中创建表似乎是个坏主意。

【讨论】:

  • 糟糕,我忘了提到我已经有一个存储过程,可以添加默认值的答案:CREATE PROCEDURE AddAnswerWithoutVal @question_id INT, @partner_id INT AS INSERT INTO Answers (question_id, partner_id) VALUES (@question_id, @partner_id)
  • 在尝试了您的建议后,我收到了Invalid object name 'inserted'. Msg 208, Level 16, State 1, Procedure AddAnswerWithVal, Line 137 Invalid object name 'inserted'.。知道为什么会这样吗?
  • @user5648283 。 . .此代码进入触发器,而不是存储过程。正如我在答案中提到的那样,插入值的存储过程只会使整个工作变得比需要的复杂得多。
【解决方案2】:

首先,你永远不会,我的意思是永远不想循环触发。循环的触发器可能会导致可怕的性能问题。您必须假设可能会插入或删除多条记录,而永远不要假设只有一条记录。触发器必须同时兼顾两者。

是的,插入的表包含刚刚插入的记录。然后,您将像在任何其他表中一样处理它们。你写的也不适用于任何普通的桌子。您不能在 EXEC 语句中引用表。

您需要做的第一件事是重写该过程,以便它根据数据集而不是单个记录进行处理。同样,尝试遍历记录是一件非常糟糕的事情。事实上,我根本不会通过另一个存储过程来执行此操作,而是将所有逻辑放在触发器中,除非有其他进程使用该过程。但是在任何情况下都需要设置逻辑,而不是逐行或一次记录。

【讨论】:

    【解决方案3】:

    语法错误的原因在这里:

        INSERT INTO QuestIds (RowNum, Id)
        SELECT DISTINCT ROW_NUMBER() OVER (ORDER BY Id) as RowNum, Id
        FROM TABLE
    

    你需要把表名放在FROM子句中,而不是关键字TABLE。

    但是您真的想在每次触发触发器时都创建这个表吗?第二次触发并且表已经存在时会发生什么?

    当我在想它的时候,这个:

    我正在尝试设置一个触发器,以便在添加新合作伙伴时, 每个问题都会为他生成默认答案。

    首先听起来像是一个次优的解决方案。如果为每个问题都定义了默认答案,为什么不只是假设,每当合作伙伴错过某个问题的答案时,他的答案就是默认答案。然后就不要为新伙伴添加任何答案,直到以后专门添加它们。

    这是我将探索的另一种方法:根本没有触发器。

    【讨论】:

    • 我对替代方法持开放态度。您可能会说,我是数据库编程的 n00b,但我正在努力学习
    猜你喜欢
    • 2015-04-21
    • 1970-01-01
    • 1970-01-01
    • 2010-09-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多