Функция обновления в триггере TSQL - PullRequest
23 голосов
/ 08 августа 2009

У меня есть вопрос об обновлении функции TSQL. Например, у меня есть таблица с полем Имя. Если я проверяю, изменено ли поле Имя или нет в триггере после обновления, то это нравится:

  if Update(Name)
  Begin
    -- process
  End

Будет ли обновление по-прежнему возвращать значение ИСТИНА, даже если имя не изменено? Следующий оператор обновления обновит его с тем же значением:

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

Если Update () возвращает TRUE, даже если значение Name не изменилось, нужно ли сравнивать значение во вставленных и удаленных виртуальных таблицах, чтобы узнать, действительно ли это значение изменилось?

Кстати, вставленные и удаленные являются виртуальными таблицами, и они могут содержать более одной строки данных, если более одной строки данных изменяются одним оператором TSQL INSERT или UPDATE. В случае более чем одной записи, совпадают ли числа строк во вставленных и удаленных виртуальных таблицах, и каково реальное значение Обновления (Имя) как ИСТИНА? Значит ли это, что хотя бы один изменен? Или Update (Name) означает, что поле Name было установлено оператором Update независимо от того, было ли изменено значение?

Используемый мной SQL-сервер - Microsoft SQL 2005.

Ответы [ 5 ]

33 голосов
/ 28 октября 2010

Триггеры сложны, и вам нужно подумать, когда вы их создаете. Триггер срабатывает один раз для каждого оператора UPDATE. Если этот оператор UPDATE обновляет несколько строк, триггер будет срабатывать только один раз. Функция UPDATE () возвращает true для столбца, когда этот столбец включен в инструкцию UPDATE. Эта функция помогает повысить эффективность триггеров, позволяя обойти логику 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

Обратите внимание, что UPDATE (SampleName) всегда будет истинным для этого оператора SQL, независимо от того, какие значения SampleName были раньше. Он возвращает true, потому что инструкция UPDATE включает столбец SampleName в разделе SET этого предложения, а не на основе значений, которые были до или после. Функция 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 (имя столбца) всегда будет иметь значение true при сохранении бизнес-объектов, поскольку все имена столбцов всегда включаются в операторы обновления. В этом случае было бы нецелесообразно использовать 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

Предложение GBN об использовании предложения IF (EXISTS (...) и помещении логики в этот оператор IF, если существующие строки были изменены, может сработать. Этот подход будет срабатывать для ВСЕХ строк, включенных в триггер, даже если только некоторые из строки были фактически изменены (что может подходить для вашего решения, но также может не подходить, если вы хотите что-то сделать только со строками, в которых изменились значения.) Если вам нужно что-то сделать со строками, в которых произошло реальное изменение, вы нужна другая логика в вашем SQL, которую он предоставил.

В моих приведенных выше примерах, когда выдается инструкция UPDATE tblSample SET SampleName = 'Foo', а четвертая строка уже является 'foo', использование подхода GBN для обновления столбца «последнее измененное время-дата» также обновляет четвертую строку, что не подходит в этом случае.

20 голосов
/ 08 августа 2009

UPDATE() может быть истинным, даже если это то же самое значение. Я бы на это не полагался лично и сравнивал бы значения.

Во-вторых, DELETED и INSERTED имеют одинаковое количество строк.

Функция Update () предназначена не для каждой строки, а для всех строк. Еще одна причина не использовать его.

Больше здесь, в MSDN , но на самом деле оно немного редкое.

После комментария:

IF EXISTS (
    SELECT
        *
    FROM
        INSERTED I
        JOIN
        DELETED D ON I.key = D.key
    WHERE
        D.valuecol <> I.valuecol --watch for NULLs!
    )
   blah
5 голосов
/ 27 апреля 2011

Я согласен, что лучший способ определить, действительно ли значение столбца изменилось (в отличие от обновления с тем же значением), состоит в сравнении значений столбца в удаленных и вставленных псевдостолях. Однако это может быть очень неприятно, если вы хотите проверить больше, чем несколько столбцов.

Вот трюк, с которым я столкнулся в каком-то коде, который я поддерживал (не знаю первоначального автора): Используйте UNION и GROUP BY с предложением HAVING, чтобы определить, какие столбцы изменились.

например, в триггере для получения идентификаторов строк, которые изменились:

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 ...
3 голосов
/ 08 октября 2013

Я думаю, что следующий код лучше, чем приведенные выше примеры, потому что он фокусируется только на столбцах, которые вы хотите проверить кратко и эффективно.

Определяет, изменилось ли значение только в указанных столбцах. Я не исследовал его производительность по сравнению с другими решениями, но он хорошо работает в моей базе данных.

Он использует оператор набора 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 в табличную переменную, на которую можно ссылаться позже.

Надеюсь, это будет интересно: -)

0 голосов
/ 08 августа 2009

Триггер обновления будет срабатывать при всех операторах обновления.затронутые строки доступны в триггере в «вставленных» и «удаленных» таблицах.Вы можете сравнить старые и новые значения, сравнив столбцы PK в двух таблицах (если у вас есть PK).Таблица фактическая остается неизменной до тех пор, пока триггер не завершит выполнение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...