天天看點

觸發器遞歸的定義SQL

背景

A表UPDATE後,取B表某列再次UPDATE A表,這樣又觸發了A表的 UPDATE  觸發器,我的目的是隻觸發一次,是否設定nested triggers 選項關閉遞歸觸發器即可?

分析

首先,必須清楚觸發器遞歸的定義,觸發器有兩種遞歸方式:

1.     直接遞歸

A表上的觸發器更改(插入/删除/更新)A表資料,導緻A表的觸發器再次觸發,這種狀況稱之為直接遞歸;

2.     間接遞歸

A表上的觸發器更新B表資料,導緻觸發B表觸發器;而B表觸發器又更改A表資料,導緻A表觸發器再次觸發,這種狀況稱之為間接遞歸

解決方法—選項配置(影響所有範圍的觸發器)

SQL Server提供了資料庫級和伺服器級配置來确定遞歸觸發器是否被允許:

1.       伺服器級(使用存儲過程sp_configure 進行配置)

server trigger recursion 選項(SQL Server 2005)決定是否允許伺服器級觸發器直接遞歸激發;當此選項設定為1 (ON,預設值)時,将允許伺服器級觸發器遞歸激發;當設定為0 (OFF) 時,伺服器級觸發器不能遞歸激發。

nested triggers選項決定是否允許觸發器間接遞歸激發;當此選項設定為1 (ON,預設值)時,将允許觸發器遞歸激發;當設定為0 (OFF) 時,觸發器不能遞歸激發。

2.       資料庫級

RECURSIVE_TRIGGERS資料庫選項設定決定是否允許資料庫中的觸發器直接遞歸觸發;預設值為OFF,不允許直接遞歸觸發。

該選項可以通過存儲過程sp_dboption設定;對于SQL Server 2005,還可以使用類似下面的T-SQL設定:

ALTER DATABASE [DbName]

    SET RECURSIVE_TRIGGERS ON

使用選項決定遞歸觸發器的行為時,需要注意的是選項設定的有效範圍:

nested triggers選項決定所有的觸發器是否間接遞歸激發,這意味着這是一個SQL Server執行個體級的選項,設定将影響所有的觸發器。

server trigger recursion選項是SQL Server 2005中才有的(SQL Server 2005才有伺服器級觸發器)。

RECURSIVE_TRIGGERS選項影響配置它的資料庫中的所有觸發器。

其他解決方法(針對特定的觸發器)

如果隻希望特定的觸發器允許或者禁止觸發器,則SQL Server沒有選項可以做到;如果确實需要這樣的功能,可以在觸發器代碼中實作控制:

1.       使用update(列名)函數

此函數适用于對 UPDATE 的控制。對于"A表UPDATE 後,取B表某列再次UPDATE A表",如果僅更新A表的某些列才觸發UPDATE B, 并且B 表再次UPDATE A表不會包含A表觸發UPDATE B的那些列,則在A表的觸發器中,使用IF UPDATE(列)來确定是否應該UPDATE B即可。

2.       使用@@NESTLEVEL

該變量值确定嵌套層數。

對于"A表update後,取B表某列再次UPDATE A表",如果觸發者不是一個存儲過程, 則UPDATE A 的A表觸發器@@NESTLEVEL = 1, 到UPDATE B時, B表觸發器 @@NESTLEVEL = 2, B表觸發器再UPDATE A時, @@NESTLEVEL = 3。

是以如果 @@NESTLEVEL >=3 時, 一般表示遞歸了(當然, 前提是UPDATE A的觸發器本身沒有兩層的遞歸,即不能是存儲過程再調用存儲過程去UPDATE A。

3.       使用 @@PROCID

該全局變量傳回調用者的object_id。如果需要A表觸發B表觸發器,而B表觸發器再觸發A表觸發器時,A表觸發器不響應;則在A表觸發器中使用它來判斷觸發者是誰,如果是B表觸發器,則不處理就行了,類似下面這樣:

IF OBJECT_ID(N'B表觸發器名稱') = @@PROCID

BEGIN

    PRINT 'B表觸發器, 不處理'

    RETURN

END

 ASA:

--Create a table

CREATE TABLE t1 ( id integer NOT NULL,

all_amt numeric ,

remarks char,

);

--insert data

INSERT INTO "DBA"."t1" ("id","all_amt","remarks") VALUES(1,1222.000000,null)

INSERT INTO "DBA"."t1" ("id","all_amt","remarks") VALUES(1,12.000000,null)

INSERT INTO "DBA"."t1" ("id","all_amt","remarks") VALUES(1,1222.000000,null)

--Want to use a statement-level triggers, at the new insert data  total  the same ID's all_amt sum, and update the total results to remarks column

--I need the results as follows::

select * from t1

id           all_amt                             remarks

1           1222.000000                         2456

1           12.000000                           2456 

1           1222.000000                         2456

Row Level Trigger:

ALTER TRIGGER "tr_calc_sum_amts_for_id_row_level_trigger" AFTER INSERT
ORDER 1 ON "DBA"."t1"
REFERENCING NEW AS new_name
FOR EACH ROW
BEGIN
    declare sum_all_amts_for_id numeric;
    select sum( t1.all_amt ) into sum_all_amts_for_id from t1 
        where id = new_name.id;
    update t1 set remarks = 
        cast( sum_all_amts_for_id  as char(50));
END
      

Statement Level Trigger:

ALTER TRIGGER "tr_calc_sum_amts_for_id_statement_level_trigger" AFTER INSERT
ORDER 1 ON "DBA"."t1"
 REFERENCING NEW AS new_name
FOR EACH STATEMENT
BEGIN
    declare curs_ids cursor for 
       select distinct id from new_name;
    declare curr_id integer;
    declare sum_all_amts_for_id numeric;
    open curs_ids;
    lp: LOOP
    fetch next curs_ids INTO curr_id;
    if sqlcode <> 0  then leave lp  end if;
    select sum( t1.all_amt ) 
       into sum_all_amts_for_id from t1 
    where id = curr_id;
    update t1 set remarks = 
         cast( sum_all_amts_for_id  as char(50))
    where id = curr_id;
    end loop;
    close curs_ids;
END