UPDATE() T-SQL 触发函数中的局部变量
Local Variable in UPDATE() T-SQL Trigger Function
我正在使用触发器来更新 table 的更改日志 table,其中包含销售信息。当有人试图在 table 上进行更新时,他们必须写一个描述,然后更新被记录(用户、时间、table、字段、Table_Unique_ID、旧值、新值)
我正在尝试对局部变量@fieldname 使用 UPDATE() 触发函数。这样我的目标是只比较更新的列的 deleted/inserted 值,而不是循环检查每一列。当我明确声明列名时,我已经能够使用 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
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;
我正在使用触发器来更新 table 的更改日志 table,其中包含销售信息。当有人试图在 table 上进行更新时,他们必须写一个描述,然后更新被记录(用户、时间、table、字段、Table_Unique_ID、旧值、新值)
我正在尝试对局部变量@fieldname 使用 UPDATE() 触发函数。这样我的目标是只比较更新的列的 deleted/inserted 值,而不是循环检查每一列。当我明确声明列名时,我已经能够使用 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
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;