Откат транзакции с триггера

В MS SQL Server 2008 R2 нам нужен триггер pre-insert и pre-update, который проверяет что-то и разрешает или откатывает (через raiserror) текущую вставку/обновление.

Вопрос. В триггере INSTEAD OF. Нужно ли явно писать вставку или обновление? Потому что мы хотим, чтобы вставка или обновление по умолчанию выполнялись и выполнялись только "precheck".

Ответ 1

Да.

Вам нужно написать явные INSERT или UPDATE.

Триггер запускает INSTEAD OF операцию DML. Если вы оставите триггер пустым, никакие действия не будут выполняться иначе, чем созданные таблицы INSERTED/DELETED и будут заполнены в tempdb.

Хотя из обсуждения в комментариях я бы вообще не использовал триггер для этого, но использовал уникальный отфильтрованный индекс CREATE UNIQUE INDEX ix ON T(a,b,c) WHERE c <> ''. Это, вероятно, будет более результативным и избежать возможных проблем с логикой при работе с concurrency.

Ответ 2

Вероятно, вам не нужен триггер INSTEAD OF, если вы не хотите заменить фактическую вставку или обновление. В вашем случае вы хотите вместо триггера FOR INSERT, UPDATE.

Этот пример запускает печать сообщения клиенту, когда кто-либо пытается добавить или изменить данные в таблице заголовков.

USE pubs
IF EXISTS (SELECT name FROM sysobjects
      WHERE name = 'reminder' AND type = 'TR')
   DROP TRIGGER reminder
GO
CREATE TRIGGER reminder
ON titles
FOR INSERT, UPDATE 
AS RAISERROR ('inserts and updates to the titles table is not allowed', 16, 1)
GO

Вы также можете использовать такие вещи, как IF EXISTS или COLUMNS_UPDATED.

Вот еще один пример использования отката.

USE pubs
IF EXISTS (SELECT name FROM sysobjects
      WHERE name = 'employee_insupd' AND type = 'TR')
   DROP TRIGGER employee_insupd
GO
CREATE TRIGGER employee_insupd
ON employee
FOR INSERT, UPDATE
AS
/* Get the range of level for this job type from the jobs table. */
DECLARE @min_lvl tinyint,
   @max_lvl tinyint,
   @emp_lvl tinyint,
   @job_id smallint
SELECT @min_lvl = min_lvl, 
   @max_lvl = max_lvl, 
   @emp_lvl = i.job_lvl,
   @job_id = i.job_id
FROM employee e INNER JOIN inserted i ON e.emp_id = i.emp_id 
   JOIN jobs j ON j.job_id = i.job_id
IF (@job_id = 1) and (@emp_lvl <> 10) 
BEGIN
   RAISERROR ('Job id 1 expects the default level of 10.', 16, 1)
   ROLLBACK TRANSACTION
END
ELSE
IF NOT (@emp_lvl BETWEEN @min_lvl AND @max_lvl)
BEGIN
   RAISERROR ('The level for job_id:%d should be between %d and %d.',
      16, 1, @job_id, @min_lvl, @max_lvl)
   ROLLBACK TRANSACTION
END

Я не уверен, что у вас есть транзакция или нет, но в вашем случае вы хотели бы что-то вроде следующего:

USE myDatabase

    IF EXISTS (SELECT name FROM sysobjects
          WHERE name = 'myTable' AND type = 'TR')
       DROP TRIGGER tr_myTrigger
    GO
    CREATE TRIGGER tr_myTrigger
    ON myTable
    FOR INSERT, UPDATE
    AS

if(exists(select * from inserted where rtrim(c) <> ''))
begin
  -- check to make sure the insert(s) are unique

    if(exists(
      select * from inserted i
      join dbo.myTable t on i.a = t.a and i.b = t.b and i.c = t.c)

    begin
      raiserror('Duplicate(s) found', 16, 1)
      rollback transaction
    end
end