【问题标题】:Local Variable in UPDATE() T-SQL Trigger FunctionUPDATE() T-SQL 触发函数中的局部变量
【发布时间】:2016-12-30 20:02:57
【问题描述】:

我正在使用触发器来更新包含销售事实的表的更改日志表。当有人试图对表进行更新时,他们必须编写描述,然后记录更新(用户、时间、表、字段、Table_Unique_ID、旧值、新值)

我正在尝试对局部变量 @fieldname 使用 UPDATE() 触发函数。目标是这样我只比较更新的列的删除/插入值,而不是循环检查每一列。当我明确声明列名时,我已经能够使用 UPDATE() 函数,但如果我将变量设置为要检查的列名,则不能。

非常感谢有关如何优化此过程的任何建议!我还有很多东西要学,这是我第一次尝试记录更改。

下面是我的完整触发器:

ALTER TRIGGER [dbo].[TR_UpdateLog]

 ON [dbo].[Test_Update_Trigger]
    AFTER  Update
 AS

Declare @description nvarchar(1000)
, @UserName nvarchar(128)
, @oldValue nvarchar(255)
, @newValue nvarchar(255)
, @UniqueID nvarchar(255)
, @fieldname nvarchar(128)
, @oname NVARCHAR(100)
, @OldSQL nvarchar(max)
, @NewSQL nvarchar(max)
, @i int
, @c int
, @numcolumns int
, @numrows int;
DECLARE @updated_table TABLE ( 
idx int Primary Key identity(1,1)
, uniqueID nvarchar(255) NULL )
;


--Require the User to submit a description for the update
Set @description = (SELECT * FROM [dbo].[Fact_Bookings_Audit_Description])
TRUNCATE TABLE [dbo].[Fact_Bookings_Audit_Description]

IF @description is null 
BEGIN
    PRINT 'You must provide a description. Use the following text:

    TRUNCATE TABLE [dbo].[Fact_Bookings_Audit_Description]
    INSERT INTO [dbo].[Fact_Bookings_Audit_Description]
    SELECT "Your Text Here--use Single Quotes"

    Copy and paste above your query, type your description, and run the update again.
    ';
    ROLLBACK TRANSACTION;
END

--Set User and Table name
Set @Username = system_User
SET @oname = (SELECT OBJECT_NAME(parent_id) from sys.triggers where object_ID = @@PROCID)


-------------------------------------------------------------
---create tables of updated items to reference for old/new values
INSERT INTO @updated_table
SELECT distinct Table_ID from DELETED

SELECT * INTO dbo.tempDelete FROM DEleted 
SELECT * INTO dbo.tempInsert FROM inserted
--------------------------------------------------
--Set variables for loop through updated items
Set @i  = 1
Set @numrows = (SELECT count(*) from @updated_table)
------------------------------------------------------------
---Set number of columns for loop through each field
Set @numcolumns = (SELECT MAx(ordinal_position) FROM information_schema.columns where table_name = @oname )

---------------------------------------------------------------
--If there was an update
IF @numrows > 0
BEGIN
    --loop through each individual updated row
    WHILE (@i <= (select max(idx) from @updated_table))
    BEGIN
        --reset the column variable
        Set @c = 1
        --loop through each column
        WHILE (@c <= @numcolumns)
        BEGIN
                Set @fieldname =    (SELECT Column_name FROM information_schema.columns 
                                        Where table_name = @oname AND ORDINAL_POSITION = @c)

                    /**This is what I want to use--
                    I would only like to do the comparison on columns which were updated, not cycle through every column**/

                    --IF UPDATE(@fieldname)
                    --BEGIN

                    --set values for old and new values as well as unique ID for log table
                    Set @UniqueID = (SELECT uniqueID from @updated_table u WHERE idx = @i)
                    Set @OldSQL = 'SELECT @oldvalue = ' + @fieldname + ' from dbo.tempdelete d WHERE d.Table_ID = ' + @UniqueID;
                    Set @NewSQL = 'SELECT @newvalue = ' + @fieldname + ' from dbo.tempInsert i WHERE i.Table_ID = ' + @UniqueID;

                    EXEC sp_executesql @oldSQL, N'@oldvalue nvarchar(128) output', @oldValue = @oldValue output
                    EXEC sp_executesql @newSQL, N'@newvalue nvarchar(128) output', @newValue = @newValue output

                    ;
                        --Insert into log table if value is changed
                        IF isnull(@oldvalue,0) <> isnull(@newvalue,0)
                        BEGIN
                            INSERT INTO dbo.Fact_Bookings_ChangeAudit (Change_Date, Change_Time, [User], Table_Name, Field, Table_ID, Oldvalue, Newvalue, [Description])
                            VALUES(cast(datediff(DAY,0,getdate())as datetime),cast(getdate() as datetime),@UserName, @oname, @fieldname, @UniqueID, @oldValue, @newValue, @description);
                        END
                    --END
                --next column
                Set @c = @c + 1
        END
    --next record
    Set @i = @i + 1
    END

END
DROP TABLE dbo.tempDelete
DROP table dbo.tempInsert

GO

【问题讨论】:

  • 我觉得里面的很多东西都是个坏主意。您也不需要mysql 标签。我知道这是您的第一个问题,但可能要评论的太多了。
  • 在我看来,这个问题跨越了两个 stackexchange 站点,这个站点和代码审查。不过,由于您正在寻找优化,您可能想在那里发帖。 codereview.stackexchange.com
  • 谢谢@shawnt00。我已经删除了 Mysql 标签。现在还在学习。如果您有任何与跟踪更改有关的好文章,我将不胜感激!
  • 谢谢@scsimon!我知道我可以做很多优化。这是一个迭代过程。你知道为什么我不能在 UPDATE() 函数中使用局部变量吗?
  • update() 中的变量没有意义。它会告诉您哪些列发生了更改以及您为何在触发器内。

标签: sql-server tsql database-trigger


【解决方案1】:
with i as (
    select
        case when updated(colA)
            then coalesce(cast(ColA as varchar(32)), 'null') end as newColA,
        case when updated(colB)
            then coalesce(cast(ColB as varchar(32)), 'null') end as newColB,
        ...
    from inserted
), d as (
    select
        case when updated(colA)
            then coalesce(cast(ColA as varchar(32)), 'null') end as oldColA,
        case when updated(colB)
            then coalesce(cast(ColB as varchar(32)), 'null') end as oldColB,
        ...
    from deleted
), old as (
    select c.Id, Col, Change
    from i unpivot (Change for Col in (newColA, newColB, ...))
    where Change is not null
), new as (
    select c.Id, Col, Change
    from d unpivot (Change for Col in (oldColA, oldColB, ...))
    where Change is not null
)
-- insert into Log
select ...
from
    old o inner join new n on n.Id = o.Id and n.Col = o.Col

列出列是正确的方法。我知道您希望在信息模式表上使用循环。我只是不认为所有动态 sql 都是一个好主意。

我认为上面的查询可能是创建要记录的行列表的起点。我没有尝试测试它。如果您尝试进行批量更新,可能会很慢。

批量更新比使用连接可能更快?

with ...
, new as (select 'i' as src ...),
, old as (select 'd' as src ...),
, combined as (select * from new union all select * from old)
select
    Id, Col
    min(case when src = 'i' then Change end) as newVal,
    min(case when src = 'd' then Change end) as oldVal,
from combined
group by Id, Col;

【讨论】:

  • 好的,这是尝试另一种策略的绝佳起点!我会让你知道情况如何。我很感激反馈。只是好奇,您认为动态 sql 的潜在缺点是什么?
  • 动态 SQL 有很多缺点,尤其是在安全性方面,触发器可能足够复杂。如果您四处搜索,我认为您在执行此类操作时会发现一些错误和奇怪的行为。虽然我无法说出一个名字。但大多数情况下,我认为如果您在简单更新期间立即触发所有这些额外的查询,那么性能将是一个大问题。
  • 在有许多需要历史记录的表的项目中,我通过查询数据字典以编程方式创建 触发器或根据具体情况进行更改以减少输入,取得了很好的效果。
  • @shawnt00 再次感谢您的反馈。我已经实施了这一策略,并注意到它对处理更大规模更新的能力产生了直接影响。
  • @ShannonSeverance 您是否有任何链接可以用来了解更多关于您在帖子中提到的内容?我认为这是一个好主意,并且愿意进行更多研究。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-06-20
  • 2018-12-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-08-02
相关资源
最近更新 更多