浅谈 KingbaseES 和 SQLServer 中的 instead of 触发器

本文基于Kingbase和SqlServer的INSTEAD OF 触发器主要功能特点进行对比浅析,同时针对SqlServer 的INSTEAD OF 触发器提出了多种kingbase环境的等价代码方案。

一,SqlServer 的INSTEAD OF 触发器

它通常用来替换触发动作的。
1,其中一个显著特点是他的作用对象通常是一张表
2,当在表上定义了INSTEAD OF触发器后,如果对表做insert、update、delete操作时触发了所定义的触发器,会直接转到触发器去执行触发器里定义的事件,不再执行之前的DML操作。触发语句更像是一个“导火索”而已,不会真正生效,真正起作用的是触发器体内的动作。

SqlServer的INSTEAD OF触发器一种典型的使用示例:

简单应用案例1


drop table t1;
drop trigger t1_trriger;``

create table t1 (c1 int,c2 int);
insert into t1 values (1,2);
insert into t1 values (3,5);

create trigger t1_trriger on t1 instead of   --用于在表t1上插入数据时对数据进行修正
insert
as
begin
	if exists(select 1 from inserted)
	and (select c1 from inserted) = 0
	begin
	   insert into dbo.t1(c1,c2) select -1,c2 from inserted;    ---对值进行修正
	end
	else begin
	   insert into dbo.t1(c1,c2) select c1,c2 from inserted;
	end
end;

insert into t1 values (0,6);
select * from t1;
--结果
-1	6      --可以看到值被修正了,原语句未生效
1	2
3	5

insert into t1 values (1,16);
select * from t1;
--结果
-1	6
1	2
3	5
1	16     --无需修正

从上面的代码可以看出,sqlserver可以是insert语句进行触发,同时在触发器体内对其进行insert操作,不会产生无限死循环,起到的是真正的“替换”操作。

二,KingbaseES 的INSTEAD OF 触发器

1,INSTEAD OF 触发器不能直接作用于表,

如果作用于表t1就会产生如下报错:
ERROR: "t1" is a table
DETAIL: Tables cannot have INSTEAD OF triggers.
通常作用于视图,视图本质上不是可更新的。
INSTEAD OF 触发器是建立在不可编辑的视图上或不可编辑视图的嵌套表列上的DML触发器。

2,INSTEAD OF触发器是用触发器体的执行代替触发语句的执行

即触发语句本身不执行,从这里来看和SqlServer的作用一样,只是不能直接作用于表。

KES的INSTEAD OF触发器一种简单的使用示例:

简单应用案例2


drop table t1;
drop view t1_id_info;
create table t1(c1 int ,c2 int);
insert into t1 values(10,11);
insert into t1 values(20,22);
create or replace view t1_id_info as select * from t1;

create trigger t1_trriger  
instead of
insert on t1_id_info
for each row
as
begin
	if inserting and :new.c1 = 0 then
		insert into t1 values(-1,:new.c2);    ---对值进行修正
	else 
		insert into t1 values(:new.c1,:new.c2);
	end if;
end;

insert into t1_id_info values(12,5);
test=# select * from t1_id_info;
test-# /
 c1 | c2
----+----
 10 | 11
 20 | 22
 12 |  5
(3 rows)

insert into t1_id_info values(0,5);

test=# select * from t1_id_info;
test-# /
 c1 | c2
----+----
 10 | 11
 20 | 22
 12 |  5
 -1 |  5
(4 rows)

三,SqlServer的INSTEAD OF触发器等价改写思考

以简单案例1为例,如果想把sqlserver中的instead of触发器通过KingbaseES的PLSQL中触发器等价改写,如何改写呢?

1,案例2本身就是案例1的一种改写方案,

当然也可以考虑其他改写方案。如下

2,before条件的DML触发改写方案

从字面理解,before 触发器是在触发操作完成之前执行,显然,before触发器(针对insert和update操作)可以修改NEW的值。

-----------before DML trigger 1(即作用于表t1又在触发体对t1进行insert插入,会形成死循环触发)

create trigger t1_trriger  
before insert on t1
as
begin
	if inserting and :new.c1 = 0 then
		insert into t1 values(-1,:new.c2);    ---对值进行修正
	else 
		insert into t1 values(:new.c1,:new.c2);
	end if;
end;

这样写肯定不可以,会形成递归,无限循环下去,最终达到stack depth限制。


test=# insert into t1_id_info values(0,5);
test-# /
ERROR:  stack depth limit exceeded
HINT:  Increase the configuration parameter "max_stack_depth" (currently 2048kB), after ensuring the platforms stack depth limit is adequate.
CONTEXT:  SQL statement "SELECT inserting and :new.c1 = 0"
PL/SQL function t1_trriger_1680177824() line 3 at IF
SQL statement "insert into t1 values(:new.c1,:new.c2)"
PL/SQL function t1_trriger_1680177824() line 6 at SQL statement
SQL statement "insert into t1 values(:new.c1,:new.c2)"
PL/SQL function t1_trriger_1680177824() line 6 at SQL statement
。。。。。。。。。(略)
SQL statement "insert into t1 values(:new.c1,:new.c2)"
PL/SQL function t1_trriger_1680177824() line 6 at SQL statement
SQL statement "insert into t1 values(:new.c1,:new.c2)"
PL/SQL function t1_trriger_1680177824() line 6 at SQL statement

-----------before DML trigger 2
值得注意的是行级触发器才能修改:NEW值

create trigger t1_trriger  
before insert on t1
for each row   --修改new值,必须是行级触发器,否则不生效的
as
begin
	if inserting and :new.c1 = 0 then
		:new.c1 := -1;    ---对值进行修正
	end if;
end;

验证:

test=# select * from t1;
test-# /
 c1 | c2
----+----
 10 | 11
 20 | 22
 12 |  5
(3 rows)

test=# insert into t1_id_info values(0,66);
test-# /
INSERT 0 1
test=# select * from t1;
test-# /
 c1 | c2
----+----
 10 | 11
 20 | 22
 12 |  5
 -1 | 66
(4 rows)

最终修改成功

3,after条件的DML触发改写方案

而after 触发器是在触发操作完成之后执行,显然从new值中着手已经没有意义,只能依靠其他方式实现“对值的修正”操作。

--使用update在插入数据后进行修正

create trigger t1_trriger  
after insert on t1
for each row
as
begin
	if inserting and :new.c1 = 0 then
		update t1 set c1 = -1 where c1 = 0 and c2 = :new.c2;     ---对值进行修正
	end if;
end;

验证:

test=# select * from t1;
test-# /
 c1 | c2
----+----
 10 | 11
 20 | 22
 12 |  5
 -1 | 66
(4 rows)

test=# insert into t1_id_info values(0,55);
test-# /
INSERT 0 1
test=# select * from t1;
test-# /
 c1 | c2
----+----
 10 | 11
 20 | 22
 12 |  5
 -1 | 66
 -1 | 55
(5 rows)

验证成功,

注意:上面的这种写法会影响历史c1值是0的数据行(c1 = 0 且 c2 = :new.c2时),如果希望不修改已有的值,最好可以根据表中的主键(如果有的话)或者唯一值进行修改。

posted @ 2023-05-19 15:19  KINGBASE研究院  阅读(112)  评论(0编辑  收藏  举报