Belirli sütunlardan herhangi birinin güncellenip güncellenmediğini kontrol etmek için COLUMNS_UPDATED nasıl kullanılır?


13

42 sütun içeren bir tablo ve bu sütunların 38 güncellendiğinde bazı şeyler yapmak bir tetik var. Yani, geri kalan 4 sütun değiştirilirse mantığı atlamalıyım.

UPDATE () işlevini kullanabilir ve büyük bir IFkoşul oluşturabilirim , ancak daha kısa bir şey yapmayı tercih ederim. COLUMNS_UPDATED kullanarak Belirli sütunların tamamının güncellenip güncellenmediğini kontrol edebilir miyim?

Örneğin, sütun 3, 5 ve 9'un güncellenip güncellenmediğini kontrol edin:

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

resim açıklamasını buraya girin

Yani, değer 20sütun için 3ve 5ve değer 1sütunu için 9ikinci byte ilk bit ayarlandığı için. İfadeyi değiştirirsem, ORsütunların 3ve / 5veya sütunun 9güncellenip güncellenmediğini kontrol eder?

ORMantığı bir bayt bağlamında nasıl uygulayabilirim ?


7
Peki, bu sütunların SETlistede geçip geçmediğini veya değerlerin gerçekten değişip değişmediğini bilmek ister misiniz ? Hem UPDATEve COLUMNS_UPDATED()size sadece eski söyle. Eğer değerler aslında değişti olmadığını bilmek istiyorsanız, uygun bir karşılaştırma yapmak gerekir insertedve deleted.
Aaron Bertrand

SUBSTRINGDöndürülen değeri bölmek için kullanmak yerine COLUMNS_UPDATED(), belgelerde gösterildiği gibi bitsel bir karşılaştırma kullanmalısınız . Tabloyu herhangi bir şekilde değiştirirseniz, tarafından döndürülen değerlerin sırasının COLUMNS_UPDATED()değişeceğini unutmayın.
Max Vernon

@AaronBertrand onlar açıkça güncellenen olmadığı halde bir kullanılarak değiştirildi değerlerini görmek gerekiyorsa, hiç aluded olarak SETveya UPDATEdeyimi, kullanmakta bakmak isteyebilirsiniz CHECKSUM()veya BINARY_CHECKSUM()hatta HASHBYTES()söz konusu sütunların üzerinde.
Max Vernon

Yanıtlar:


18

CHECKSUM()Gerçek değerlerin değiştirilip değiştirilmediğini karşılaştırmak için oldukça basit bir yöntem olarak kullanabilirsiniz . CHECKSUM()sayısı ve türü belirsiz olan bir geçirilen değerler listesinde sağlama toplamı oluşturur. Dikkat edin, bunun gibi sağlama toplamlarını karşılaştırmanın küçük bir şansı vardır. Bununla başa çıkamıyorsanız, HASHBYTESbunun yerine 1'i kullanabilirsiniz .

Aşağıdaki örnek AFTER UPDATE, TriggerTestyalnızca Data1 veya Data2 sütunlarındaki değerlerden biri değiştiğinde tabloda yapılan değişikliklerin geçmişini korumak için bir tetikleyici kullanır . Data3Değişiklik olursa herhangi bir işlem yapılmaz.

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

resim açıklamasını buraya girin

COLUMNS_UPDATED () işlevini kullanmakta ısrar ediyorsanız , söz konusu sütunların sıra değerini sabit kodlamamalısınız, çünkü tablo tanımı değişebilir, bu da sabit kodlanmış değerleri geçersiz kılabilir. Sistem tablolarını kullanarak çalışma zamanında değerin ne olması gerektiğini hesaplayabilirsiniz. COLUMNS_UPDATED()Sütun, ifadeden etkilenen HERHANGİ bir satırda değiştirilirse , işlevin verilen sütun biti için true değerini döndürdüğünü unutmayın UPDATE TABLE.

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;

resim açıklamasını buraya girin

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

resim açıklamasını buraya girin

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

resim açıklamasını buraya girin

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

resim açıklamasını buraya girin

Bu demo, tarih tablosuna belki de eklenmemesi gereken satırları ekler. Satırların Data1sütunları bazı satırlar Data3için güncellendi ve sütun bazı satırlar için güncellendi. Bu tek bir ifade olduğundan, tüm satırlar tetikleyiciden tek bir geçişle işlenir. Karşılaştırmanın bir Data1parçası olan bazı satırlar güncellendiğinden, COLUMNS_UPDATED()tetikleyici tarafından görülen tüm satırlar TriggerHistorytabloya eklenir . Senaryonuz için bu "yanlış" ise, imleç kullanarak her satırı ayrı ayrı işlemeniz gerekebilir.

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;

TriggerResultTablo şimdi onlar (o tablodaki iki sütun) kesinlikle hiçbir değişiklik göstermesi nedeniyle aittir değil mi benziyor bazı potansiyel yanıltıcı satır var. Aşağıdaki görüntüdeki 2. satır kümesinde, TriggerTestID 7 değiştirilmiş gibi görünen tek satırdır. Diğer satırlarda yalnızca Data3sütun güncellendi; ancak toplu işteki bir satır Data1güncellendiğinden, tüm satırlar TriggerResulttabloya eklenir .

resim açıklamasını buraya girin

Alternatif olarak, @AaronBertrand ve @srutzky'nin de belirttiği gibi, insertedve deletedsanal tablolardaki gerçek verilerin bir karşılaştırmasını yapabilirsiniz . Her iki tablonun yapısı aynı olduğundan, EXCEPTilgilendiğiniz kesin sütunların değiştiği satırları yakalamak için tetikleyicide bir cümle kullanabilirsiniz :

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 - bkz /programming/297960/hash-collision-what-are-the-chances HASHBYTES hesaplama da çarpışmaları neden olabileceğini yok denecek kadar az şans discsussion için. Preshing de bu sorunun iyi bir analizine sahiptir.


2
Bu iyi bir bilgidir, ancak "Bununla başa çıkamıyorsanız HASHBYTESbunun yerine kullanabilirsiniz ." yanıltıcıdır. Yanlış negatiflere sahip olma olasılığının daha düşükHASHBYTES olduğu doğrudur (kullanılan algoritmanın boyutuna göre değişme olasılığı), ancak göz ardı edilemez. Herhangi bir hash fonksiyonu her zaman çarpışma potansiyeline sahip olacaktır çünkü bilgi azalması muhtemeldir. Değişiklik olmadığından emin olmanın tek yolu ve tablolarını karşılaştırmak ve dize verisi ise bir harmanlama kullanmaktır . Karmaları karşılaştırmak sadece farklılıklar için kesinlik verir. CHECKSUMINSERTEDDELETED_BIN2
Solomon Rutzky

2
@srutzky Çarpışmalar konusunda endişeleneceksek, bunun olasılığını da belirtelim. stackoverflow.com/questions/297960/…
Dave

1
@Dave karma kullanmayın demiyorum: değişen öğeleri tanımlamak için kullanın. Demek istediğim,% 0'dan büyük olasılıkla, garanti edildiğini (alıntıladığım şu andaki ifade) ima etmek yerine belirtilmelidir, böylece okuyucular daha iyi anlarlar. Evet, bir çarpışma olasılığı çok, çok küçüktür, ancak sıfır değildir ve kaynak verilerin boyutuna göre değişir. İki değerin aynı olduğunu garanti etmem gerekirse, kontrol etmek için birkaç ekstra CPU döngüsü harcayacağım. Karma boyutuna bağlı olarak, karma ve bir BIN2 karşılaştırması arasında çok mükemmel bir fark olmayabilir, bu yüzden% 100 doğru olanı seçin.
Solomon Rutzky

1
Bu dipnotu eklediğiniz için teşekkürler (+1). Şahsen, aşırı basit olduğu için bu cevaptan başka bir kaynak kullanırım. İki sorun vardır: 1) kaynak değer boyutları büyüdükçe olasılık artar. Dün gece SO ve diğer sitelerdeki birkaç gönderiyi okudum ve görüntüleri üzerinde kullanan bir kişi 25.000 girişten sonra çarpışma bildirdi ve 2) olasılık, göreceli risk, bir karma kullanan birisinin olmayacağını söyleyecek bir şey olmadığını 10 bin girdide birkaç kez çarpışmalara giriyor. Şans = şans. Eğer şans ;-) olduğunun farkındaysanız güvenmek tamam.
Solomon Rutzky
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.