Puede utilizar CHECKSUM()
una metodología bastante simple para comparar valores reales para ver si se modificaron. CHECKSUM()
generará una suma de comprobación en una lista de valores pasados, de los cuales el número y el tipo son indeterminados. Cuidado, hay una pequeña posibilidad de comparar sumas de comprobación como esta que dará como resultado falsos negativos. Si no puede hacer frente a eso, se puede utilizar HASHBYTES
en lugar de 1 .
El siguiente ejemplo utiliza un AFTER UPDATE
activador para retener un historial de modificaciones realizadas en la TriggerTest
tabla solo si alguno de los valores en las columnas Data1
o Data2
cambia. Si Data3
cambia, no se toman medidas.
USE tempdb;
IF COALESCE(OBJECT_ID('dbo.TriggerTest'), 0) <> 0
BEGIN
DROP TABLE dbo.TriggerTest;
END
CREATE TABLE dbo.TriggerTest
(
TriggerTestID INT NOT NULL
CONSTRAINT PK_TriggerTest
PRIMARY KEY CLUSTERED
IDENTITY(1,1)
, Data1 VARCHAR(10) NULL
, Data2 VARCHAR(10) NOT NULL
, Data3 DATETIME NOT NULL
);
IF COALESCE(OBJECT_ID('dbo.TriggerResult'), 0) <> 0
BEGIN
DROP TABLE dbo.TriggerResult;
END
CREATE TABLE dbo.TriggerResult
(
TriggerTestID INT NOT NULL
, Data1OldVal VARCHAR(10) NULL
, Data1NewVal VARCHAR(10) NULL
, Data2OldVal VARCHAR(10) NULL
, Data2NewVal VARCHAR(10) NULL
);
GO
IF COALESCE(OBJECT_ID('dbo.TriggerTest_AfterUpdate'), 0) <> 0
BEGIN
DROP TRIGGER TriggerTest_AfterUpdate;
END
GO
CREATE TRIGGER TriggerTest_AfterUpdate
ON dbo.TriggerTest
AFTER UPDATE
AS
BEGIN
INSERT INTO TriggerResult
(
TriggerTestID
, Data1OldVal
, Data1NewVal
, Data2OldVal
, Data2NewVal
)
SELECT d.TriggerTestID
, d.Data1
, i.Data1
, d.Data2
, i.Data2
FROM inserted i
LEFT JOIN deleted d ON i.TriggerTestID = d.TriggerTestID
WHERE CHECKSUM(i.Data1, i.Data2) <> CHECKSUM(d.Data1, d.Data2);
END
GO
INSERT INTO dbo.TriggerTest (Data1, Data2, Data3)
VALUES ('blah', 'foo', GETDATE());
UPDATE dbo.TriggerTest
SET Data1 = 'blah', Data2 = 'fee'
WHERE TriggerTestID = 1;
SELECT *
FROM dbo.TriggerTest;
SELECT *
FROM dbo.TriggerResult
Si insiste en usar la función COLUMNS_UPDATED () , no debe codificar el valor ordinal de las columnas en cuestión, ya que la definición de la tabla puede cambiar, lo que puede invalidar los valores codificados. Puede calcular cuál debería ser el valor en tiempo de ejecución utilizando las tablas del sistema. Tenga en cuenta que la COLUMNS_UPDATED()
función devuelve verdadero para el bit de columna dado si la columna se modifica en CUALQUIER fila afectada por la UPDATE TABLE
instrucción.
USE tempdb;
IF COALESCE(OBJECT_ID('dbo.TriggerTest'), 0) <> 0
BEGIN
DROP TABLE dbo.TriggerTest;
END
CREATE TABLE dbo.TriggerTest
(
TriggerTestID INT NOT NULL
CONSTRAINT PK_TriggerTest
PRIMARY KEY CLUSTERED
IDENTITY(1,1)
, Data1 VARCHAR(10) NULL
, Data2 VARCHAR(10) NOT NULL
, Data3 DATETIME NOT NULL
);
IF COALESCE(OBJECT_ID('dbo.TriggerResult'), 0) <> 0
BEGIN
DROP TABLE dbo.TriggerResult;
END
CREATE TABLE dbo.TriggerResult
(
TriggerTestID INT NOT NULL
, Data1OldVal VARCHAR(10) NULL
, Data1NewVal VARCHAR(10) NULL
, Data2OldVal VARCHAR(10) NULL
, Data2NewVal VARCHAR(10) NULL
);
GO
IF COALESCE(OBJECT_ID('dbo.TriggerTest_AfterUpdate'), 0) <> 0
BEGIN
DROP TRIGGER TriggerTest_AfterUpdate;
END
GO
CREATE TRIGGER TriggerTest_AfterUpdate
ON dbo.TriggerTest
AFTER UPDATE
AS
BEGIN
DECLARE @ColumnOrdinalTotal INT = 0;
SELECT @ColumnOrdinalTotal = @ColumnOrdinalTotal
+ POWER (
2
, COLUMNPROPERTY(t.object_id,c.name,'ColumnID') - 1
)
FROM sys.schemas s
INNER JOIN sys.tables t ON s.schema_id = t.schema_id
INNER JOIN sys.columns c ON t.object_id = c.object_id
WHERE s.name = 'dbo'
AND t.name = 'TriggerTest'
AND c.name IN (
'Data1'
, 'Data2'
);
IF (COLUMNS_UPDATED() & @ColumnOrdinalTotal) > 0
BEGIN
INSERT INTO TriggerResult
(
TriggerTestID
, Data1OldVal
, Data1NewVal
, Data2OldVal
, Data2NewVal
)
SELECT d.TriggerTestID
, d.Data1
, i.Data1
, d.Data2
, i.Data2
FROM inserted i
LEFT JOIN deleted d ON i.TriggerTestID = d.TriggerTestID;
END
END
GO
--this won't result in rows being inserted into the history table
INSERT INTO dbo.TriggerTest (Data1, Data2, Data3)
VALUES ('blah', 'foo', GETDATE());
SELECT *
FROM dbo.TriggerResult;
--this will insert rows into the history table
UPDATE dbo.TriggerTest
SET Data1 = 'blah', Data2 = 'fee'
WHERE TriggerTestID = 1;
SELECT *
FROM dbo.TriggerTest;
SELECT *
FROM dbo.TriggerResult;
--this WON'T insert rows into the history table
UPDATE dbo.TriggerTest
SET Data3 = GETDATE()
WHERE TriggerTestID = 1;
SELECT *
FROM dbo.TriggerTest;
SELECT *
FROM dbo.TriggerResult
--this will insert rows into the history table, even though only
--one of the columns was updated
UPDATE dbo.TriggerTest
SET Data1 = 'blum'
WHERE TriggerTestID = 1;
SELECT *
FROM dbo.TriggerTest;
SELECT *
FROM dbo.TriggerResult;
Esta demostración inserta filas en la tabla de historial que quizás no deberían insertarse. Las filas han tenido su Data1
columna actualizada para algunas filas, y la Data3
columna ha sido actualizada para algunas filas. Como se trata de una declaración única, todas las filas se procesan con una sola pasada a través del desencadenador. Como algunas filas se han Data1
actualizado, lo que forma parte de la COLUMNS_UPDATED()
comparación, todas las filas vistas por el desencadenador se insertan en la TriggerHistory
tabla. Si esto es "incorrecto" para su escenario, es posible que deba manejar cada fila por separado, utilizando un cursor.
INSERT INTO dbo.TriggerTest (Data1, Data2, Data3)
SELECT TOP(10) LEFT(o.name, 10)
, LEFT(o1.name, 10)
, GETDATE()
FROM sys.objects o
, sys.objects o1;
UPDATE dbo.TriggerTest
SET Data1 = CASE WHEN TriggerTestID % 6 = 1 THEN Data2 ELSE Data1 END
, Data3 = CASE WHEN TriggerTestID % 6 = 2 THEN GETDATE() ELSE Data3 END;
SELECT *
FROM dbo.TriggerTest;
SELECT *
FROM dbo.TriggerResult;
La TriggerResult
tabla ahora tiene algunas filas potencialmente engañosas que parecen no pertenecer, ya que no muestran absolutamente ningún cambio (en las dos columnas de esa tabla). En el segundo conjunto de filas en la imagen a continuación, TriggerTestID 7 es el único que parece haber sido modificado. Las otras filas solo tenían la Data3
columna actualizada; sin embargo, desde la una fila en el lote había Data1
actualizado, todas las filas se insertan en la TriggerResult
tabla.
Alternativamente, como @AaronBertrand y @srutzky señalaron, se puede realizar una comparación de los datos reales de los inserted
y deleted
virtuales tablas. Dado que la estructura de ambas tablas es idéntica, puede usar una EXCEPT
cláusula en el disparador para capturar filas donde las columnas precisas que le interesan han cambiado:
IF COALESCE(OBJECT_ID('dbo.TriggerTest_AfterUpdate'), 0) <> 0
BEGIN
DROP TRIGGER TriggerTest_AfterUpdate;
END
GO
CREATE TRIGGER TriggerTest_AfterUpdate
ON dbo.TriggerTest
AFTER UPDATE
AS
BEGIN
;WITH src AS
(
SELECT d.TriggerTestID
, d.Data1
, d.Data2
FROM deleted d
EXCEPT
SELECT i.TriggerTestID
, i.Data1
, i.Data2
FROM inserted i
)
INSERT INTO dbo.TriggerResult
(
TriggerTestID,
Data1OldVal,
Data1NewVal,
Data2OldVal,
Data2NewVal
)
SELECT i.TriggerTestID
, d.Data1
, i.Data1
, d.Data2
, i.Data2
FROM inserted i
INNER JOIN deleted d ON i.TriggerTestID = d.TriggerTestID
END
GO
1: consulte /programming/297960/hash-collision-what-are-the-chances para ver una discusión sobre la pequeña posibilidad de que el cálculo HASHBYTES también pueda provocar colisiones. Preshing también tiene un análisis decente de este problema.
SET
lista o si los valores realmente cambiaron? AmbosUPDATE
yCOLUMNS_UPDATED()
solo te digo lo primero. Si desea saber si los valores realmente cambiaron, deberá hacer una comparación adecuada deinserted
ydeleted
.