2009-08-08 72 views
22

我有一個關於TSQL函數更新的問題。例如,我有一個字段名稱的表。如果我檢查,如果字段名稱改變或不能在更新後觸發喜歡你的:TSQL觸發器中的更新函數

if Update(Name) 
    Begin 
    -- process 
    End 

將更新仍然返回TRUE即使名稱沒有改變?下面的更新語句將使用相同的值更新:

SELECT @v_Name = Name From MyTable Where Id = 1; 
    Update MyTable Set Name = @v_Name where Id = 1; 

如果更新()返回TRUE甚至名稱的值不會改變,我必須插入和刪除虛擬表中的值進行比較,以找出價值是否真的改變了?

順便說一句,插入和刪除是虛擬表,如果多個數據行由一個TSQL INSERT或UPDATE語句更改,它們可能包含多行數據。在多於一條記錄的情況下,插入和刪除的虛擬表中的行數是否相同,Update(Name)的真正含義是TRUE?這是否意味着至少有一個改變了?或更新(名稱)是否意味着名稱的字段已由Update語句設置,而不管該值是否更改?

我使用的SQL服務器是Microsoft SQL 2005

回答

20

UPDATE()可以是真實的,即使是相同的值。我不會依賴它,並會比較價值。

其次,DELETEDINSERTED具有相同的行數。

Update()函數不是每行,而是跨所有行。另一個不使用它的原因。

More here in MSDN,但它真的有點稀疏。

評論後:

IF EXISTS (
    SELECT 
     * 
    FROM 
     INSERTED I 
     JOIN 
     DELETED D ON I.key = D.key 
    WHERE 
     D.valuecol <> I.valuecol --watch for NULLs! 
    ) 
    blah 
+0

謝謝。 SQL服務器提供的其他任何可用選項來查找更改?或者我必須遍歷每一行才能找出變化? – 2009-08-08 14:49:57

+1

只需加入密鑰上的2個表,不需要loosp需要 – gbn 2009-08-08 14:50:33

+1

這就是'刪除'和'**插入**'虛擬表,對吧?我從來沒有聽說過「更新」虛擬表格 – 2009-08-08 14:51:28

0

更新觸發條件會在所有更新語句。受影響的行在「已插入」和「已刪除」表中的觸發器中可用。您可以通過比較兩個表中的PK列(如果有PK)來比較舊值和新值。在觸發器完成執行之前,實際的表保持不變。

+0

錯誤地說「實際」表格保持不變,直到觸發完成。如果觸發器是一個AFTER觸發器,那麼在觸發器被調用的時候表格將被更新。當觸發器完成時,外部環境只會看到更新。 – 2016-03-02 22:39:29

33

觸發器很棘手,當您創建觸發器時需要大量思考。每個UPDATE語句觸發一次觸發器。如果該UPDATE語句更新多行,則該觸發器將只觸發一次。當該列包含在UPDATE語句中時,UPDATE()函數爲列返回true。該功能有助於提高觸發器的效率,即使更新語句中未包含該列,您也可以避開SQL邏輯。它不會告訴您值是否在給定行中的列發生更改。

下面是一個示例表...

CREATE TABLE tblSample 
(
    SampleID INT PRIMARY KEY, 
    SampleName VARCHAR(10), 
    SampleNameLastChangedDateTime DATETIME, 
    Parent_SampleID INT 
) 

如果下面的SQL是對本表中使用:

UPDATE tblSample SET SampleName = 'hello' 

..和一個AFTER INSERT,UPDATE觸發器在效果,這種特殊的SQL語句總是按如下方式評估UPDATE函數...

IF UPDATE(SampleName) --aways evaluates to TRUE 
IF UPDATE(SampleID) --aways evaluates to FALSE 
IF UPDATE(Parent_SampleID) --aways evaluates to FALSE 

請注意,對於此SQL語句,UPDATE(SampleName)始終爲true,無論以前的SampleName值如何。它返回true,因爲UPDATE語句在該子句的SET部分中包含了SampleName列,而不是基於值之前或之後的值。 UPDATE()函數不會確定值是否更改。如果您想根據值是否更改來執行操作,則需要使用SQL並比較插入和刪除的行。

這裏有一個方法來同步保持最新更新列:

--/* 
IF OBJECT_ID('dbo.tgr_tblSample_InsertUpdate', 'TR') IS NOT NULL 
    DROP TRIGGER dbo.tgr_tblSample_InsertUpdate 
GO 
--*/ 

CREATE TRIGGER dbo.tgr_tblSample_InsertUpdate ON dbo.tblSample 
    AFTER INSERT, UPDATE 
AS 
BEGIN --Trigger 

    IF UPDATE(SampleName) 
    BEGIN 
     UPDATE tblSample SET 
     SampleNameLastChangedDateTime = CURRENT_TIMESTAMP 
     WHERE 
     SampleID IN (SELECT Inserted.SampleID 
       FROM Inserted LEFT JOIN Deleted ON Inserted.SampleID = Deleted.SampleID 
       WHERE COALESCE(Inserted.SampleName, '') <> COALESCE(Deleted.SampleName, '')) 
    END 

END --Trigger 

的邏輯來確定是否已更新,該行是在WHERE子句中的上方。這是你需要做的真正的檢查。我的邏輯是使用COALESCE來處理NULL值和INSERTS。

... 
WHERE 
    SampleID IN (SELECT Inserted.SampleID 
       FROM Inserted LEFT JOIN Deleted ON Inserted.SampleID = Deleted.SampleID 
       WHERE COALESCE(Inserted.SampleName, '') <> COALESCE(Deleted.SampleName, '')) 

請注意,IF UPDATE()檢查用於幫助提高沒有更新SampleName列時觸發器的效率。如果一個SQL語句更新了實例的Parent_SampleID列,那麼IF UPDATE(SampleName)檢查將幫助避開該IF語句中更復雜的邏輯,當它不需要運行時。考慮在適當時使用UPDATE(),但不要出於錯誤原因。

也意識到根據你的架構,UPDATE函數可能對你沒有用處。如果代碼體系結構使用中間層,那麼在保存對象時總是使用業務對象中的值更新表的一行中的所有列,但觸發器中的UPDATE()函數變得毫無用處。在這種情況下,您的代碼可能總是使用從中間層發佈的每個UPDATE語句更新所有列。既然如此,當保存業務對象時,UPDATE(columnname)函數總是計算爲真,因爲所有列名都始終包含在更新語句中。在這種情況下,在觸發器中使用UPDATE()並不會有幫助,並且在大多數情況下只是在該觸發器中額外的開銷。

下面是一些SQL與上述觸發播放:

INSERT INTO tblSample 
(
    SampleID, 
    SampleName 
) 
SELECT 1, 'One' 
UNION SELECT 2, 'Two' 
UNION SELECT 3, 'Three' 

GO 
SELECT SampleID, SampleName, SampleNameLastChangedDateTime FROM tblSample 

/* 
SampleID SampleName SampleNameLastChangedDateTime 
----------- ---------- ----------------------------- 
1  One 2010-10-27 14:52:42.567 
2  Two 2010-10-27 14:52:42.567 
3  Three 2010-10-27 14:52:42.567 
*/ 

GO 

INSERT INTO tblSample 
(
    SampleID, 
    SampleName 
) 
SELECT 4, 'Foo' 
UNION SELECT 5, 'Five' 

GO 
SELECT SampleID, SampleName, SampleNameLastChangedDateTime FROM tblSample 
/* 
SampleID SampleName SampleNameLastChangedDateTime 
----------- ---------- ----------------------------- 
1  One 2010-10-27 14:52:42.567 
2  Two 2010-10-27 14:52:42.567 
3  Three 2010-10-27 14:52:42.567 
4  Foo 2010-10-27 14:52:42.587 
5  Five 2010-10-27 14:52:42.587 
*/ 

GO 

UPDATE tblSample SET SampleName = 'Foo' 

SELECT SampleID, SampleName, SampleNameLastChangedDateTime FROM tblSample 
/* 
SampleID SampleName SampleNameLastChangedDateTime 
----------- ---------- ----------------------------- 
1  Foo 2010-10-27 14:52:42.657 
2  Foo 2010-10-27 14:52:42.657 
3  Foo 2010-10-27 14:52:42.657 
4  Foo 2010-10-27 14:52:42.587 
5  Foo 2010-10-27 14:52:42.657 
*/ 
GO 

UPDATE tblSample SET SampleName = 'Not Prime' WHERE SampleID IN (1,4) 

SELECT SampleID, SampleName, SampleNameLastChangedDateTime FROM tblSample 
/* 
SampleID SampleName SampleNameLastChangedDateTime 
----------- ---------- ----------------------------- 
1  Not Prime 2010-10-27 14:52:42.680 
2  Foo  2010-10-27 14:52:42.657 
3  Foo  2010-10-27 14:52:42.657 
4  Not Prime 2010-10-27 14:52:42.680 
5  Foo  2010-10-27 14:52:42.657 
*/ 

--Clean up... 
DROP TRIGGER dbo.tgr_tblSample_InsertUpdate 
DROP TABLE tblSample 

用戶GBN曾建議如下:

IF EXISTS (
    SELECT 
     * 
    FROM 
     INSERTED I 
     JOIN 
     DELETED D ON I.key = D.key 
    WHERE 
     D.valuecol <> I.valuecol --watch for NULLs! 
    ) 
    blah 

使用IF(中GBN的建議EXISTS(...條款,並把IF語句中的邏輯是否存在被更改的行可以工作,即使只有一些行實際發生了更改(這可能適用於您的解決方案,但也可能不是如果你只想做某件事,那就適當)。如果您需要對發生實際更改的行執行某些操作,則需要您提供的SQL中的不同邏輯。

在我上面的示例中,當發佈UPDATE tblSample SET SampleName ='Foo'語句並且第四行已經是'foo'時,使用GBN的方法更新「上次更改的日期時間」列也會更新第四行在這種情況下這不合適。

5

我同意確定一個列值是否已經實際更改(而不是用相同的值進行更新)的最佳方法是對刪除和插入的僞表中的列值進行比較。然而,如果你想檢查多個欄目,這可能是一個真正的痛苦。

下面是我在維護的代碼中遇到的一個技巧(不知道原作者): 使用帶有HAVING子句的UNION和GROUP BY來確定哪些列已更改。

例如,在觸發,讓已更改的行的ID:

SELECT SampleID 
FROM 
    (
     SELECT SampleID, SampleName 
     FROM deleted 

     -- NOTE: UNION, not UNION ALL. UNION by itself removes duplicate 
     -- rows. UNION ALL includes duplicate rows. 
     UNION 

     SELECT SampleID, SampleName 
     FROM inserted 
    ) x 
GROUP BY SampleID 
HAVING COUNT(*) > 1 

這是太多的工作,當你,如果一列已經改變了只檢查。但是,如果你正在檢查10個或20列的UNION方法比

WHERE COALESCE(Inserted.Column1, '') <> COALESCE(Deleted.Column1, '') 
    OR COALESCE(Inserted.Column2, '') <> COALESCE(Deleted.Column2, '') 
    OR COALESCE(Inserted.Column3, '') <> COALESCE(Deleted.Column3, '') 
    OR ... 
2

我認爲下面的代碼要比上面的例子,因爲它側重於只是要檢查在列更好的工作少了很多簡潔高效的方式。

它確定值是否僅在指定的列中發生了變化。與其他解決方案相比,我沒有調查過它的性能,但它在我的數據庫中運行良好。

它使用EXCEPT集合運算符返回左查詢中沒有在正確查詢中找到的任何行。此代碼可以用於INSERT和UPDATE觸發器。

「PrimaryKeyID」列是表的主鍵(可以是多列),並且需要啓用兩個集之間的匹配。

-- Only do trigger logic if specific field values change. 
IF EXISTS(SELECT PrimaryKeyID 
       ,Column1 
       ,Column7 
       ,Column10 
      FROM inserted 
      EXCEPT 
      SELECT PrimaryKeyID 
       ,Column1 
       ,Column7 
       ,Column10 
      FROM deleted) -- Tests for modifications to fields that we are interested in 
BEGIN 
      -- Put code here that does the work in the trigger 

END 

如果你想在隨後的觸發邏輯使用更改的行,我通常把EXCEPT查詢的結果爲可以在以後引用的表變量。

我希望這是有趣的:-)