条件 old 不等于 new 的 Oracle 触发器以错误 PLS-00049、PL/SQL: ORA-00933 结束

Posted

技术标签:

【中文标题】条件 old 不等于 new 的 Oracle 触发器以错误 PLS-00049、PL/SQL: ORA-00933 结束【英文标题】:Oracle Trigger with condition old not equal to new ending up with errors PLS-00049, PL/SQL: ORA-00933 【发布时间】:2018-10-24 19:51:58 【问题描述】:

假设我在名为 EMPLOYEES 的主表上有数据,示例数据如下:

员工

+--------+----------+
| EMP_ID | EMP_NAME |
+--------+----------+
|    100 | Smith    |
|    200 | Clark    |
+--------+----------+

现在,如果表中插入了新数据或更新了现有数据;然后数据如下所示: 员工

+--------+----------+
| EMP_ID | EMP_NAME |             
+--------+----------+
|    100 | Blake    | <---- UPDATE with different value
|    200 | Clark    | <---- UPDATE with same value
|    300 | Mary     | <---- INSERT
+--------+----------+

其中 EMP_ID 为 100 和 200 的行是更新操作,而 EMP_ID 为 300 的行作为其新记录插入。

我编写了触发器来捕获 EMP_NAME 列中的插入或更新更改,仅当旧 emp_name 不等于相应 EMP_ID 的新 emp_name 时。 (我不想在名为 EMPLOYEE_AUDITS 的审计表中有重复的 EMP_ID)。下面是触发器:

CREATE OR REPLACE TRIGGER employee_audits_tr 
INSERT OR UPDATE ON employees 
FOR EACH ROW 
BEGIN 
IF ( inserting OR updating AND :old.emp_name <> :new.emp_name ) THEN 
    INSERT INTO employee_audits(emp_id, old_emp_name,new_emp_name) 
                         VALUES(emp_id, :old.emp_name, :new.emp_name ) 
                         WHERE emp_id = :new.emp_id; 
END IF; 
END;

根据上面提供的数据,尝试在 EMPLOYEE_AUDITS 上获得如下输出 EMPLOYEE_AUDITS

+----+--------+--------------+--------------+
| ID | EMP_ID | OLD_EMP_NAME | NEW_EMP_NAME |
+----+--------+--------------+--------------+
|  1 |    100 | Smith        | Blake        |
|  2 |    300 | NULL         | Mary         |
+----+--------+--------------+--------------+

对于下一次迭代,当 EMPLOYEES 表再次使用新数据集更新时,如下所示: 员工

+--------+----------+
| EMP_ID | EMP_NAME |                             
+--------+----------+
|    100 | Karla    | <---- UPDATE with NEW value  
|    200 | Clark    | <---- UPDATE with same value
|    300 | James    | <---- UPDATE with NEW value
|    400 | Sofia    | <---- INSERT                
+--------+----------+

EMPLOYEE_AUDITS 表应包含以下值:EMPLOYEE_AUDITS

+----+--------+--------------+--------------+
| ID | EMP_ID | OLD_EMP_NAME | NEW_EMP_NAME |
+----+--------+--------------+--------------+
|  1 |    100 | Blake        | Karla        |
|  2 |    300 | Mary         | James        |
|  3 |    400 | NULL         | Sofia        |
+----+--------+--------------+--------------+

使用触发器,出现如下错误:

错误 PLS-00049、PL/SQL:ORA-00933

在此先感谢大家,并感谢任何帮助。

谢谢, 里查

【问题讨论】:

【参考方案1】:

触发码错误:

触发器声明中没有 BEFORE 关键字 您已经说过它会在插入或更新之前触发;您也不必将其放入 IF 中 如果你确实把它放在那里,那么要小心:当有ORs 时,如果你不将运算符括在括号中,你会破坏一切。那将是if (updating or inserting) and (:old.emp_name &lt;&gt; :new.emp_name) then ...。 注意 NULL 值!插入时,没有 OLD 值 INSERT INTO 中没有 WHERE 子句 您错过了填充AUDIT 表中的ID 列;我选择使用序列

上面所说的组合(尤其是缺少BEGIN 和多余的WHERE)导致ORA-00933(SQL 命令未正确结束)错误。

这是一个完整的示例:

SQL> create table employees (emp_id number, emp_name varchar2(20));

Table created.

SQL> insert into employees
  2  select 100, 'smith' from dual union all
  3  select 200, 'clark' from dual;

2 rows created.

SQL> create table employee_audits
  2    (id number, emp_id number, old_emp_name varchar2(20), new_emp_name varchar2(20));

Table created.

SQL> create sequence seq_audit;

Sequence created.

SQL>
SQL> create or replace trigger employee_audits_tr
  2    before insert or update on employees
  3    for each row
  4  begin
  5    if nvl(:old.emp_name, '-1') <> nvl(:new.emp_name, '-1')
  6    then
  7       insert into employee_audits
  8         (id, emp_id, old_emp_name, new_emp_name)
  9         values
 10         (seq_audit.nextval, :new.emp_id, :old.emp_name, :new.emp_name);
 11    end if;
 12  end;
 13  /

Trigger created.

测试:

SQL> update employees set emp_name = 'blake' where emp_id = 100;

1 row updated.

SQL> update employees set emp_name = 'clark' where emp_id = 200;

1 row updated.

SQL> insert into employees values (300, 'mary');

1 row created.

SQL> select * From employee_audits;

        ID     EMP_ID OLD_EMP_NAME         NEW_EMP_NAME
---------- ---------- -------------------- --------------------
         1        100 smith                blake
         2        300                      mary

SQL>

[编辑:每位员工只保留一行]

在我看来,您不应该这样做 - 如果您丢失了之前的所有修改,这是什么样的审核?无论如何,这是触发代码:

首先更新一行 如果EMP_ID 不存在,UPDATE 不会做任何事情,SQL%ROWCOUNT 将是 0 在这种情况下,INSERT 一行

.

SQL> create or replace trigger employee_audits_tr
  2    before insert or update on employees
  3    for each row
  4  begin
  5    update employee_audits set
  6      old_emp_name = :old.emp_name,
  7      new_emp_name = :new.emp_name
  8      where emp_id = :new.emp_id ;
  9
 10    if sql%rowcount = 0 then
 11       insert into employee_audits (id, emp_id, old_emp_name, new_emp_name)
 12       values (seq_audit.nextval, :new.emp_id, :old.emp_name, :new.emp_name);
 13    end if;
 14  end;
 15  /

Trigger created.

SQL>

【讨论】:

谢谢@Littlefoot。我非常感谢您的帮助。非常感谢您的详细解释,重写代码并为我节省了大量时间。我用 AFTER insert 语句编写了代码,我想在格式化时我可能已经删除了。我的错。我有一个问题:如果我必须在 EMPLOYEES 表上添加另一列“PAYMENT_HOLD”,其中包含 Y 和 N 值,并且如果我必须在 EMPLOYEE_AUDITS 表上捕获这些更改,我必须只修改 IF 部分对吗?如下:if ((nvl(:old.emp_name, '-1') &lt;&gt; nvl(:new.emp_name, '-1')) or (nvl(:old.payment_hold, '-1') &lt;&gt; nvl(:new.payment_hold, '-1'))) 不客气。作为一个新的条件,是的 - 如果你想检查它,使用 OR。 真棒@Littlefoot。再次感谢您的快速回复。非常感谢您的时间和帮助 没问题:) 嗨@Littlefoot,我用不同的数据集尝试了几次迭代并注意到,由于为插入语句指定了条件,我得到了重复的 EMP_ID。如果 emp_id 已经存在,我应该包含更新语句吗?我只需要 EMPLOYEES_AUDIT 表中唯一的 emp_id。我应该将代码修改为BEGIN... IF insert then... IF nvl(:old.emp_name, '-1') &lt;&gt; nvl(:new.emp_name, '-1') then.. insert into... IF 更新然后IF update then... nvl(:old.emp_name, '-1') &lt;&gt; nvl(:new.emp_name, '-1') then.. update where... 抱歉问了太多问题

以上是关于条件 old 不等于 new 的 Oracle 触发器以错误 PLS-00049、PL/SQL: ORA-00933 结束的主要内容,如果未能解决你的问题,请参考以下文章

oracle怎样建立一个一模一样的表,急~在线等

oracle new 和old 关键字

触发器简介

[转]oracle触发器与:new,:old的使用

oracle update触发器如何获取被修改的字段

使用 :new 和 :old 在更新和插入之前触发 Oracle