Подтвердить что ты не робот

Функция обновления в триггере TSQL

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

  if Update(Name)
  Begin
    -- process
  End

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

  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) как TRUE? Означает ли это, что по крайней мере один из них изменен? Или Update (Name) означает, что поле Name было задано оператором Update независимо от того, изменилось ли значение?

Я использую SQL-сервер Microsoft SQL 2005.

4b9b3361

Ответ 1

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

Ответ 2

Триггеры сложны, и вам нужно думать навалом, когда вы их создаете. Триггер срабатывает один раз для каждого оператора 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 (columnname) всегда будет оценивать значение 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-подхода для обновления столбца "последнее измененное время" также обновит четвертую строку, что в данном случае было бы неуместным.

Ответ 3

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

Вот трюк, который я встретил в некотором коде, который я поддерживал (не знаю оригинального автора): Используйте 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 ...

Ответ 4

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

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

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

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

Ответ 5

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