Aka*_*ava 17 sql sql-server triggers database-replication sql-update
我知道COLUMNS_UPDATED,我需要一些快速的快捷方式(如果有人做了,我已经制作了一个,但如果有人可以节省我的时间,我会恭维它)
我需要基本上只有更新列值的XML,我需要这个用于复制目的.
SELECT*FROM inserted为我提供了每一列,但我只需要更新的列.
像下面这样......
CREATE TRIGGER DBCustomers_Insert
ON DBCustomers
AFTER UPDATE
AS
BEGIN
DECLARE @sql as NVARCHAR(1024);
SET @sql = 'SELECT ';
I NEED HELP FOR FOLLOWING LINE ...., I can manually write every column, but I need
an automated routin which can work regardless of column specification
for each column, if its modified append $sql = ',' + columnname...
SET @sql = $sql + ' FROM inserted FOR XML RAW';
DECLARE @x as XML;
SET @x = CAST(EXEC(@sql) AS XML);
.. use @x
END
Run Code Online (Sandbox Code Playgroud)
Ste*_*ner 21
我有另一个完全不同的解决方案,根本不使用COLUMNS_UPDATED,也不依赖于在运行时构建动态SQL.(您可能希望在设计时使用动态SQL,但那是另一个故事.)
基本上,您从插入和删除的表开始,将每个表都取消,以便为每个表留下唯一的键,字段值和字段名称列.然后你加入这两个并过滤掉任何改变的东西.
这是一个完整的工作示例,包括一些测试调用以显示记录的内容.
-- -------------------- Setup tables and some initial data --------------------
CREATE TABLE dbo.Sample_Table (ContactID int, Forename varchar(100), Surname varchar(100), Extn varchar(16), Email varchar(100), Age int );
INSERT INTO Sample_Table VALUES (1,'Bob','Smith','2295','bs@example.com',24);
INSERT INTO Sample_Table VALUES (2,'Alice','Brown','2255','ab@example.com',32);
INSERT INTO Sample_Table VALUES (3,'Reg','Jones','2280','rj@example.com',19);
INSERT INTO Sample_Table VALUES (4,'Mary','Doe','2216','md@example.com',28);
INSERT INTO Sample_Table VALUES (5,'Peter','Nash','2214','pn@example.com',25);
CREATE TABLE dbo.Sample_Table_Changes (ContactID int, FieldName sysname, FieldValueWas sql_variant, FieldValueIs sql_variant, modified datetime default (GETDATE()));
GO
-- -------------------- Create trigger --------------------
CREATE TRIGGER TriggerName ON dbo.Sample_Table FOR DELETE, INSERT, UPDATE AS
BEGIN
SET NOCOUNT ON;
--Unpivot deleted
WITH deleted_unpvt AS (
SELECT ContactID, FieldName, FieldValue
FROM
(SELECT ContactID
, cast(Forename as sql_variant) Forename
, cast(Surname as sql_variant) Surname
, cast(Extn as sql_variant) Extn
, cast(Email as sql_variant) Email
, cast(Age as sql_variant) Age
FROM deleted) p
UNPIVOT
(FieldValue FOR FieldName IN
(Forename, Surname, Extn, Email, Age)
) AS deleted_unpvt
),
--Unpivot inserted
inserted_unpvt AS (
SELECT ContactID, FieldName, FieldValue
FROM
(SELECT ContactID
, cast(Forename as sql_variant) Forename
, cast(Surname as sql_variant) Surname
, cast(Extn as sql_variant) Extn
, cast(Email as sql_variant) Email
, cast(Age as sql_variant) Age
FROM inserted) p
UNPIVOT
(FieldValue FOR FieldName IN
(Forename, Surname, Extn, Email, Age)
) AS inserted_unpvt
)
--Join them together and show what's changed
INSERT INTO Sample_Table_Changes (ContactID, FieldName, FieldValueWas, FieldValueIs)
SELECT Coalesce (D.ContactID, I.ContactID) ContactID
, Coalesce (D.FieldName, I.FieldName) FieldName
, D.FieldValue as FieldValueWas
, I.FieldValue AS FieldValueIs
FROM
deleted_unpvt d
FULL OUTER JOIN
inserted_unpvt i
on D.ContactID = I.ContactID
AND D.FieldName = I.FieldName
WHERE
D.FieldValue <> I.FieldValue --Changes
OR (D.FieldValue IS NOT NULL AND I.FieldValue IS NULL) -- Deletions
OR (D.FieldValue IS NULL AND I.FieldValue IS NOT NULL) -- Insertions
END
GO
-- -------------------- Try some changes --------------------
UPDATE Sample_Table SET age = age+1;
UPDATE Sample_Table SET Extn = '5'+Extn where Extn Like '221_';
DELETE FROM Sample_Table WHERE ContactID = 3;
INSERT INTO Sample_Table VALUES (6,'Stephen','Turner','2299','st@example.com',25);
UPDATE Sample_Table SET ContactID = 7 where ContactID = 4; --this will be shown as a delete and an insert
-- -------------------- See the results --------------------
SELECT *, SQL_VARIANT_PROPERTY(FieldValueWas, 'BaseType') FieldBaseType, SQL_VARIANT_PROPERTY(FieldValueWas, 'MaxLength') FieldMaxLength from Sample_Table_Changes;
-- -------------------- Cleanup --------------------
DROP TABLE dbo.Sample_Table; DROP TABLE dbo.Sample_Table_Changes;
Run Code Online (Sandbox Code Playgroud)
所以不要乱搞bigint位域和关节溢出问题.如果您知道要在设计时比较的列,则不需要任何动态SQL.
在缺点方面,输出采用不同的格式,所有字段值都转换为sql_variant,第一个可以通过再次旋转输出来修复,第二个可以通过根据您的知识重新调整到所需的类型来修复.表的设计,但这两者都需要一些复杂的动态sql.这些都可能不是XML输出中的问题.这个问题类似于以相同的格式返回输出.
编辑:查看下面的注释,如果您有一个可以更改的自然主键,那么您仍然可以使用此方法.您只需使用NEWID()函数添加默认使用GUID填充的列.然后,您可以使用此列代替主键.
您可能希望向此字段添加索引,但由于触发器中已删除和插入的表位于内存中,因此可能无法使用它并可能对性能产生负面影响.
pod*_*sta 18
在触发器内部,您可以COLUMNS_UPDATED()像这样使用以获得更新的值
-- Get the table id of the trigger
--
DECLARE @idTable INT
SELECT @idTable = T.id
FROM sysobjects P JOIN sysobjects T ON P.parent_obj = T.id
WHERE P.id = @@procid
-- Get COLUMNS_UPDATED if update
--
DECLARE @Columns_Updated VARCHAR(50)
SELECT @Columns_Updated = ISNULL(@Columns_Updated + ', ', '') + name
FROM syscolumns
WHERE id = @idTable
AND CONVERT(VARBINARY,REVERSE(COLUMNS_UPDATED())) & POWER(CONVERT(BIGINT, 2), colorder - 1) > 0
Run Code Online (Sandbox Code Playgroud)
但是当你有一个超过62列的表时,这段代码就会失败.. Arth.Overflow ...
这是最终版本,它处理超过62列,但只提供更新列的数量.可以很容易地与'syscolumns'链接以获取名称
DECLARE @Columns_Updated VARCHAR(100)
SET @Columns_Updated = ''
DECLARE @maxByteCU INT
DECLARE @curByteCU INT
SELECT @maxByteCU = DATALENGTH(COLUMNS_UPDATED()),
@curByteCU = 1
WHILE @curByteCU <= @maxByteCU BEGIN
DECLARE @cByte INT
SET @cByte = SUBSTRING(COLUMNS_UPDATED(), @curByteCU, 1)
DECLARE @curBit INT
DECLARE @maxBit INT
SELECT @curBit = 1,
@maxBit = 8
WHILE @curBit <= @maxBit BEGIN
IF CONVERT(BIT, @cByte & POWER(2,@curBit - 1)) <> 0
SET @Columns_Updated = @Columns_Updated + '[' + CONVERT(VARCHAR, 8 * (@curByteCU - 1) + @curBit) + ']'
SET @curBit = @curBit + 1
END
SET @curByteCU = @curByteCU + 1
END
Run Code Online (Sandbox Code Playgroud)
我已经把它做为简单的“单线”。没有使用、枢轴、循环、许多变量等,这使它看起来像过程式编程。应该使用SQL来处理数据集:-),解决方法是:
DECLARE @sql as NVARCHAR(1024);
select @sql = coalesce(@sql + ',' + quotename(column_name), quotename(column_name))
from INFORMATION_SCHEMA.COLUMNS
where substring(columns_updated(), columnproperty(object_id(table_schema + '.' + table_name, 'U'), column_name, 'columnId') / 8 + 1, 1) & power(2, -1 + columnproperty(object_id(table_schema + '.' + table_name, 'U'), column_name, 'columnId') % 8 ) > 0
and table_name = 'DBCustomers'
-- and column_name in ('c1', 'c2') -- limit to specific columns
-- and column_name not in ('c3', 'c4') -- or exclude specific columns
SET @sql = 'SELECT ' + @sql + ' FROM inserted FOR XML RAW';
DECLARE @x as XML;
SET @x = CAST(EXEC(@sql) AS XML);
Run Code Online (Sandbox Code Playgroud)
它使用COLUMNS_UPDATED,处理超过八列 - 它可以处理任意数量的列。
它注意正确的列顺序,应该使用COLUMNPROPERTY获得。
它基于视图COLUMNS,因此它可能仅包含或排除特定的列。