【问题标题】:SQL Server 2008 Trigger ErrorsSQL Server 2008 触发器错误
【发布时间】:2013-06-27 18:27:08
【问题描述】:

我需要一些关于我无法工作的数据库触发器的帮助。披露:我不是 DBA,也没有任何专业知识,但我的任务是完成这项工作..

也就是说,这里是问题陈述:

我有一个数据库,其中包含一些我们想要跟踪更新/插入/删除的表。触发器应该捕获某些列的“之前”和“之后”值,同时获取一些附加信息(如下所述)并将它们添加到versioncontrol 表中。

我已经根据教程和各种网站上提供的其他信息汇总了我认为应该工作的内容,但是无论我在尝试创建触发器时做什么,它总是会引发错误:

消息 311,级别 16,状态 1,过程 tr_taskconstructs_U,第 38 行
不能在“已插入”和“已删除”表中使用 text、ntext 或 image 列。

我应用触发器的表的列布局非常简单:

ResourceID (PK, nvarchar(38))
AML (ntext, null)

我想捕获对AML 列的更改并将更改插入到表中,如下所示:

id (int, not null),
ResourceID (nvarchar(38)),
TableName (nvarchar(50)),
DateTime (datetime),
Type (varchar(20)),
Before (ntext),
After (ntext)

我的代码如下:

CREATE TRIGGER tr_taskconstructs_U ON taskconstructs AFTER UPDATE
AS 
BEGIN

SET NOCOUNT ON;

    DECLARE @before nvarchar(max), 
        @after nvarchar(max), 
        @resource nvarchar(50)

IF UPDATE(AML)
BEGIN
    SELECT @before = d.AML,
            @after = i.AML,
            @resource = d.ResourceID
    FROM inserted i
        INNER JOIN deleted d on d.ResourceID = i.ResourceID

    INSERT INTO versioncontrol
    VALUES (@resource, 'taskconstructs', GetDate(), 'Update', @before, @after)

END

END
GO

它总是因上述错误而失败。我尝试更改变量等的数据类型,结果相同。我试过注释掉几乎所有的东西,它也有同样的错误。我显然错过了一些东西,但无法弄清楚是什么。

任何帮助将不胜感激。

【问题讨论】:

    标签: sql sql-server sql-server-2008 triggers


    【解决方案1】:

    更改表以改用 nvarchar(max) 数据类型。 Ntext 无论如何都已被弃用,不应继续使用

    【讨论】:

    • 感谢 SQLMenace - 我已将版本控制表中的列更新为使用 varchar 或 nvarchar,但我仍然遇到同样的错误。
    • @user2093445 您需要更改您的taskconstructs 表。触发器无法监控 ntext 列上的更改
    • 啊,我明白了。我在版本控制表上更改了它。我不确定是否可以更改任务构造表,因为该应用程序是商业应用程序并且我无法更改源代码。如果我无法更改源表,是否有任何选择?
    【解决方案2】:

    就像在另一个答案ntext 中所说,以及textimage 是触发器无法真正使用的已弃用数据类型。它们将在 SQL Server 的未来版本中删除,因此您最好的解决方案是将它们分别更改为 NVARCHAR(MAX)VARCHAR(MAX)VARBINARY(MAX)

    http://msdn.microsoft.com/en-us/library/ms189799.aspx

    但是,如果您目前无法做到这一点 - INSTEAD OF 触发器可以与它们一起使用,因此您可以尝试拦截更新并从触发器执行它们,并在此过程中填充您的日志表。

    但是,在我向您展示该示例之前,我必须指出您在现有触发器中存在的一个大错误 - 您编写的查询仅适用于来自 INSERTED 的单行不好,因为UPDATE 一次可以经常更新超过 1 行。即使您的应用程序没有设计为允许它,您也不应该编写触发器,因为它始终是单行。

    所以你的触发器应该是这样的: (如果/在您更改该列后应该看起来像这样)

    CREATE TRIGGER tr_taskconstructs_U ON taskconstructs AFTER UPDATE
    AS 
    BEGIN
    
    SET NOCOUNT ON;
    
    IF UPDATE(AML)
    BEGIN
    
        INSERT INTO versioncontrol
        SELECT ResourceID, 'taskconstructs', GetDate(), 'Update'. d.AML, i.AML
        FROM inserted i
        INNER JOIN deleted d on d.ResourceID = i.ResourceID
    
    END
    
    END
    GO
    

    现在,对于INSTEAD OF TRIGGER:

    CREATE TRIGGER tr_taskconstructs_U ON taskconstructs INSTEAD OF UPDATE
    AS 
    BEGIN
    
        --insert log
        INSERT INTO versioncontrol 
        SELECT i.ResourceID, 'taskconstructs', GetDate(), 'Update', d.AML, i.AML
        FROM inserted i
         INNER JOIN taskconstructs d on d.ResourceID = i.ResourceID
    
        --update actual data
        UPDATE t 
        SET t.AML = i.AML
        from taskconstructs t
        INNER JOIN INSERTED i ON i.ResourceID = t.ResourceID 
    
    END
    GO
    

    SQLFiddle DEMO

    【讨论】:

    • @Nenand - 您的 INSTEAD OF UPDATE 解决方案运行良好。由于该应用程序是商业应用程序,因此我将使用它运行,并且我不知道更改其中一个系统表将如何影响应用程序。也非常感谢你抓住了多行……你让我看起来像个英雄。 :)
    • @user2093445 YW! :) 至于更改列类型 - 虽然我绝不建议您在不先咨询负责非生产环境中的应用程序和测试的人的情况下做任何事情。 - 我认为在不更改应用程序的情况下从ntext 切换到nvarchar(max) 应该没有问题。 sqlfiddle.com/#!6/2d9b1/2
    猜你喜欢
    • 2015-01-31
    • 1970-01-01
    • 2014-01-19
    • 2014-03-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多