Làm cách nào để sử dụng COLUMNS_UPDATED để kiểm tra xem có bất kỳ cột nào được cập nhật không?


12

Tôi có bảng với 42 cột và một trình kích hoạt sẽ thực hiện một số nội dung khi 38 cột này được cập nhật. Vì vậy, tôi cần bỏ qua logic nếu 4 cột còn lại bị thay đổi.

Tôi có thể sử dụng hàm UPDATE () và tạo một IFđiều kiện lớn , nhưng thích làm một cái gì đó ngắn hơn. Sử dụng COLUMNS_UPDATED tôi có thể kiểm tra xem tất cả các cột nhất định có được cập nhật không?

Ví dụ: kiểm tra xem cột 3, 5 và 9 có được cập nhật không:

  IF 
  (
    (SUBSTRING(COLUMNS_UPDATED(),1,1) & 20 = 20)
     AND 
    (SUBSTRING(COLUMNS_UPDATED(),2,1) & 1 = 1) 
  )
    PRINT 'Columns 3, 5 and 9 updated';

nhập mô tả hình ảnh ở đây

Vì vậy, giá trị 20cho cột 35, và giá trị 1cho cột 9vì nó được đặt trong bit đầu tiên của byte thứ hai. Nếu tôi thay đổi câu lệnh thành ORnó sẽ kiểm tra xem cột 35hoặc cột 9có được cập nhật không?

Làm thế nào có thể áp dụng ORlogic trong ngữ cảnh của một byte?


7
Chà, bạn có muốn biết nếu các cột đó được đề cập trong SETdanh sách, hoặc nếu các giá trị thực sự thay đổi? Cả hai UPDATECOLUMNS_UPDATED()chỉ cho bạn biết trước đây. Nếu bạn muốn biết nếu các giá trị thực sự thay đổi, bạn sẽ cần phải so sánh đúng inserteddeleted.
Aaron Bertrand

Thay vì sử dụng SUBSTRINGđể phân chia biểu mẫu trả về giá trị COLUMNS_UPDATED(), bạn nên sử dụng so sánh theo bit, như thể hiện trong tài liệu . Xin lưu ý rằng nếu bạn thay đổi bảng theo bất kỳ cách nào, thứ tự của các giá trị được trả về COLUMNS_UPDATED()sẽ thay đổi.
Max Vernon

Như @AaronBertrand đã nói, nếu bạn cần xem các giá trị đã được thay đổi mặc dù chúng không được cập nhật rõ ràng bằng cách sử dụng một câu lệnh SEThoặc UPDATE, bạn có thể muốn xem xét bằng cách sử dụng CHECKSUM()hoặc BINARY_CHECKSUM(), hoặc thậm chí HASHBYTES()qua các cột được đề cập.
Max Vernon

Câu trả lời:


17

Bạn có thể sử dụng CHECKSUM()như một phương pháp khá đơn giản để so sánh các giá trị thực tế để xem chúng có bị thay đổi hay không. CHECKSUM()sẽ tạo tổng kiểm tra qua danh sách các giá trị được truyền vào, trong đó số lượng và loại không xác định. Coi chừng, có một cơ hội nhỏ so sánh tổng kiểm như thế này sẽ dẫn đến kết quả âm tính giả. Nếu bạn không thể đối phó với điều đó, bạn có thể sử dụng HASHBYTESthay vì 1 .

Ví dụ dưới đây sử dụng trình AFTER UPDATEkích hoạt để giữ lại lịch sử sửa đổi được thực hiện cho TriggerTestbảng chỉ khi một trong hai giá trị trong Data1 hoặc Data2 cột thay đổi. Nếu Data3thay đổi, không có hành động được thực hiệ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
    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

nhập mô tả hình ảnh ở đây

Nếu bạn khăng khăng sử dụng hàm COLUMNS_UPDATED () , bạn không nên mã hóa giá trị thứ tự của các cột trong câu hỏi, vì định nghĩa bảng có thể thay đổi, có thể làm mất hiệu lực (các) giá trị được mã hóa cứng. Bạn có thể tính toán giá trị nên có trong thời gian chạy bằng cách sử dụng các bảng hệ thống. Xin lưu ý rằng COLUMNS_UPDATED()hàm trả về true cho bit cột đã cho nếu cột bị sửa đổi trong bất kỳ hàng nào bị ảnh hưởng bởi UPDATE TABLEcâu lệnh.

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;

nhập mô tả hình ảnh ở đây

--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;

nhập mô tả hình ảnh ở đây

--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

nhập mô tả hình ảnh ở đây

--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;

nhập mô tả hình ảnh ở đây

Bản demo này chèn các hàng vào bảng lịch sử có lẽ không nên chèn. Các hàng đã được Data1cập nhật cột cho một số hàng và đã Data3cập nhật cột cho một số hàng. Vì đây là một câu lệnh đơn, tất cả các hàng được xử lý bằng một lần chuyển qua trình kích hoạt. Vì một số hàng đã Data1được cập nhật, là một phần của COLUMNS_UPDATED()so sánh, tất cả các hàng được xem bởi trình kích hoạt sẽ được chèn vào TriggerHistorybảng. Nếu điều này là "không chính xác" cho kịch bản của bạn, bạn có thể cần xử lý từng hàng riêng biệt bằng cách sử dụng một con trỏ.

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;

Các TriggerResultbảng hiện nay có một số hàng có khả năng gây hiểu lầm mà hình như họ không thuộc vì họ thể hiện hoàn toàn không có sự thay đổi (với hai cột trong bảng đó). Trong tập hợp hàng thứ 2 trong hình ảnh bên dưới, TriggerTestID 7 là hàng duy nhất trông giống như được sửa đổi. Các hàng khác chỉ có Data3cột được cập nhật; tuy nhiên vì một hàng trong lô đã Data1được cập nhật, tất cả các hàng được chèn vào TriggerResultbảng.

nhập mô tả hình ảnh ở đây

Cách khác, như @AaronBertrand và @srutzky đã chỉ ra, bạn có thể thực hiện so sánh dữ liệu thực tế trong các bảng inserteddeletedảo. Vì cấu trúc của cả hai bảng giống hệt nhau, bạn có thể sử dụng một EXCEPTmệnh đề trong trình kích hoạt để chụp các hàng trong đó các cột chính xác mà bạn quan tâm đã thay đổi:

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 - xem /programming/297960/hash-collision-what-are-the-chances cho một sự bất đồng về cơ hội nhỏ nhoi mà tính toán HASHBYTES cũng có thể dẫn đến va chạm. Preshing có một phân tích tốt về vấn đề này là tốt.


2
Đây là thông tin tốt, nhưng "Nếu bạn không thể đối phó với điều đó, bạn có thể sử dụng HASHBYTESthay thế." là sai lệch. Đúng HASHBYTESít có khả năng có âm tính giả hơn CHECKSUM(khả năng thay đổi kích thước của thuật toán được sử dụng), nhưng không thể loại trừ. Bất kỳ chức năng băm nào cũng sẽ luôn có khả năng xảy ra va chạm vì nó có khả năng bị giảm thông tin. Cách duy nhất để chắc chắn không có thay đổi là so sánh các bảng INSERTEDDELETEDbảng và sử dụng _BIN2đối chiếu nếu đó là dữ liệu chuỗi. So sánh băm chỉ mang lại sự chắc chắn cho sự khác biệt.
Solomon Rutzky 17/03/2016

2
@srutzky Nếu chúng ta lo lắng về va chạm, hãy nói rõ khả năng xảy ra. stackoverflow.com/questions/297960/ trộm
Dave

1
@Dave Tôi không nói không sử dụng băm: sử dụng chúng để xác định các mục đã thay đổi. Quan điểm của tôi là, vì khả năng> 0%, nên được nêu rõ hơn là ngụ ý rằng nó được đảm bảo (từ ngữ hiện tại mà tôi đã trích dẫn) để người đọc hiểu rõ hơn về nó. Có, xác suất xảy ra va chạm là rất, rất nhỏ, nhưng không phải bằng không và thay đổi theo kích thước của dữ liệu nguồn. Nếu tôi cần đảm bảo rằng hai giá trị là như nhau, tôi sẽ dành thêm một vài chu kỳ CPU để kiểm tra. Tùy thuộc vào kích thước băm, có thể không có nhiều khác biệt hoàn hảo giữa so sánh băm và so sánh BIN2, vì vậy hãy chọn tỷ lệ chính xác 100%.
Solomon Rutzky

1
Cảm ơn bạn đã đưa vào chú thích đó (+1). Cá nhân, tôi sẽ sử dụng một tài nguyên khác với câu trả lời cụ thể đó vì nó quá đơn giản. Có hai vấn đề: 1) khi kích thước giá trị nguồn càng lớn, xác suất càng tăng. Tôi đã đọc qua một số bài đăng trên SO và các trang web khác tối qua, và một người sử dụng điều này trên hình ảnh đã báo cáo các vụ va chạm sau 25.000 mục và 2) xác suất chỉ là, rủi ro tương đối, không có gì để nói rằng ai đó sử dụng hàm băm sẽ không chạy vào va chạm một vài lần trong 10k mục. Cơ hội = may mắn. Bạn có thể dựa vào nếu bạn nhận thức được đó là may mắn ;-).
Solomon Rutzky
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.