服务器 频道

在DB2 UDB中使用SQL过程语言来操作触发器

1 : CREATE TRIGGER verify_state
2 : NO CASCADE BEFORE UPDATE ON orders_t
3 : REFERENCING OLD AS o NEW AS n
4 : FOR EACH ROW MODE DB2SQL
5 : BEGIN ATOMIC
6 :   IF o.status=''PENDING'' and n.status IN (''SHIPPED'',''CANCELLED'') THEN
7 :     -- valid state
8 :   ELSEIF o.status=''SHIPPED'' and
9 :       n.status =''DELIVERED'' THEN
10:     -- valid state
11:   ELSEIF o.status=''DELIVERED'' and
12:       n.status = ''COMPLETED'' THEN
13:     -- valid state
14:   ELSE
15:     SIGNAL SQLSTATE ''80001'' (''Invalid State Transition'');
16:   END IF;
17: END

  本例中,触发器名为 verify_state。它是在对表 orders_t 进行更新之前被激活的。另一个不同就是我们用 o 和 n 作为列名限定符分别引用旧值和新值。

  第 5 行到第 16 行中的这种状态转换方式是直接的。如果转换没有执行,我们则假定有错误并引发一个应用程序错误,发出消息“Invalid State Transition”。操作将被拒绝。当然,判断逻辑也可写成如下形式:

  IF NOT((o.status=''PENDING'' and n.status IN (''SHIPPED'',''CANCELLED'')) OR
    (o.status=''SHIPPED'' and n.status = ''DELIVERED'' OR
    (o.status=''DELIVERED'' and n.status = ''COMPLETED'')) THEN
  SIGNAL SQLSTATE ''80001'' (''Invalid State Transition'')
END IF;

  ...前面的写法是为了意思更清晰并能充分说明 IF/THEN/ELSE 结构的句法。

  Delete 触发器

  对于最后一条业务规则,我们将说明触发器的更简单形式,这在 DB2 UDB 7.2 之前就已经可用了。先将业务规则分为以下两部分:

  3a)“如果订单没有被取消就不能被删除。”
  3b)“记录已删除的订单信息以备审计。”

  下面是实施 3a 的触发器:

  1 : CREATE TRIGGER restrict_delete
2 : NO CASCADE BEFORE DELETE ON orders_t
3 : REFERENCING OLD AS o
4 : FOR EACH ROW MODE DB2SQL
5 : WHEN (o.status <> ''CANCELLED'')
6 :   SIGNAL SQLSTATE ''80003'' (''Cannot Delete an order that has not been cancelled'')

  “AFTER” 触发器

  对于规则 3b,我们将用一个 AFTER 触发器来记录表orders_t中的删除操作。

  1 : CREATE TRIGGER log_delete
2 : AFTER DELETE ON orders_t
3 : REFERENCING OLD AS o
4 : FOR EACH ROW MODE DB2SQL
5 :   INSERT INTO delete_log_t VALUES (
         ''rder #?|| CHAR (o.order_id) ||
         ''as deleted on ?|| CHAR(CURRENT TIMESTAMP));

  该 delete 触发器和前面两个触发器的不同之处在于,这里没有用到 BEGIN ATOMIC 和 END。因为如果在触发器中只有一条 SQL 语句,就不是必需的。上面这个触发器显然没有记录太多有用的信息来支持审计,但演示了如何通过触发器使对一个表的插入操作来触发完成对另一个表的插入。任何时候执行删除操作且满足WHEN 子句中的条件都将激活该触发器。如果你将 WHEN 子句完全删掉(“AFTER”触发器上面),该触发器将一直处于激活状态。

  测试规则

  先测试业务规则 1,我们将插入两份来自于 Nancy 的饰品订单。这位客户的赊购最高限额仅为 $100,因此第一份订单被成功插入,而第二份则操作失败。(每份订单的价格均为 $90。)

  Insert into orders_t values (nextval for ord_seq, 1, 1, 9, 10.0, ''ENDING'')
Insert into orders_t values (nextval for ord_seq, 1, 1, 9, 10.0, ''ENDING'')

  我们可以在前面操作的基础上测试业务规则 2。鉴于订单的有效状态转换(前面已提到),状态为“PENDING”的订单可转换为“SHIPPED”。一旦订单物品已经发送,就不能被取消了。下面的第一个更新操作可以成功,但第二个将无法通过。

  Update orders_t set status=''HIPPED'' where order_id=1
Update orders_t set status=''ANCELLED'' where order_id=1

  我们只需试图去删除刚才插入的订单就可以测试业务规则 3。下面的删除语句将失败,因为该订单的状态不是“CANCELLED”。而且由于前面的这个触发器触发失败,用于记录删除操作的 AFTER DELETE 触发器也不会被激活。

  Delete from orders_t where order_id=1

  如果要测试这个用于记录删除操作的触发器,我们可以插入一份不超过 Nancy 最高赊购限额的订单,然后取消并删除它。

  Insert into orders_t values (nextval for ord_seq, 1, 1, 1, 10.0, ''PENDING'')
Update orders_t set status=''CANCELLED'' where order_id=(prevval for ord_seq)
Delete from orders_t where order_id=(prevval for ord_seq)
Select * from delete_log_t

  性能技巧

  •   BEFBEFORE 触发器应该用于修改用户输入的值或用于生成新值,如生成主键。在 AFTER 触发器中试图修改转换表中的行更为复杂。

  •   DB2 是一个功能强大的关系引擎。然而,目前它并没有优化过程逻辑(控制)语句和其他 SQL 语句。

      例如 verify_credit 触发器也可以改写成下面这样:

      1 : CREATE TRIGGER verify_credit

    2 : NO CASCADE BEFORE INSERT ON orders_t

    3 : REFERENCING NEW AS n

    4 : FOR EACH STATEMENT MODE DB2SQL

    5 : WHEN ((SELECT SUM(price * quantity) FROM orders_t

    6 :       WHERE cust_id = n.cust_id

    7 :         AND status NOT IN (''COMPLETED'', ''CANCELLED''))

    8 :      + n.price * n.quality

    9 :     > (SELECT credit FROM customer_t WHERE cust_id=n.cust_id))

    10: SIGNAL SQLSTATE ''80000'' (''Order Exceeds credit line'')

  • 当你需要比较新值与旧值时,对于更新来说,最好选用 FOR EACH ROW 触发器,而不是 FOR EACH STATEMENT 触发器。使用 FOR EACH ROW 触发器可以获得更好的性能,因为每一行都包含用于比较的值,即可完成触发器的操作,而不需要使用存储在转换表中的所有新值和旧值。

  其他技巧

  • 支持 WHILE 循环。
  • GET DIAGNOSTICS <var : int> = ROW_COUNT 可用于确定在触发器主体中最近调用的 update,delete 或 insert 语句影响了多少行记录。
  • 不支持 SELECT .... INTO 语法。要将多列属性值传给多个变量应使用 use SET (x,y) = (SELECT x_col, y_col FROM mytable)。
  • 避免使用递归触发器。递归触发器是指该触发器会被自身主体中相同的语句激活。例如,如果我们在表 mytable 上定义一个 DELETE 触发器,而其主体中也包含一个 mytable 上的DELETE 语句,这就是一个递归触发器。若不小心编码,就会产生问题。即使需要用到递归,也应限于使用单个迭代。
  • 如果在一个表上定义多个触发器(也就是说,两个 BEFORE INSERT 触发器被定义),他们将按照被创建的次序来被执行。当然,BEFORE 触发器总是在 AFTER 触发器之前被激活而不必考虑其创建次序。表的其他约束条件(如主/外键约束,唯一性约束以及检查约束)同样会在 BEFORE 触发器之后,AFTER 触发器之前被检查。

  结束语

  触发器对于实施业务规则十分有效,而这些业务规则恰恰是数据库应用程序的中心。


0
相关文章