PL/SQL 触发器不适用于全新插入的数据
PL/SQL Trigger doesn't work on brand new inserted data
我创建了一个触发器来检查是否有人更新了薪水或在员工中插入了新员工 table 使用检查程序,下面是我的程序代码和我的触发器:
CREATE OR REPLACE PROCEDURE check_salary (pjobid employees.job_id%type, psal employees.salary%type)
IS
BEGIN
FOR i in (SELECT min_salary, max_salary
FROM jobs
WHERE job_id = pjobid)
LOOP
IF psal < i.min_salary OR psal > i.max_salary THEN
RAISE_APPLICATION_ERROR(-20001, 'Invalid salary ' || psal || '. Salaries for job ' || pjobid || ' must be between ' || i.min_salary || ' and ' || i.max_salary);
ELSE
DBMS_OUTPUT.PUT_LINE('Salary is okay!');
END IF;
END LOOP;
END check_salary;
CREATE OR REPLACE TRIGGER check_salary_trg
BEFORE INSERT OR UPDATE ON employees
FOR EACH ROW
WHEN (new.salary != old.salary OR new.job_id != old.job_id)
BEGIN
check_salary(:old.job_id, :new.salary);
END check_salary_trg;
现在,当我尝试更新已在 table 上声明的特定员工的工资时,它可以正常工作(结果是评论的那个):
UPDATE employees
SET salary = 2800
WHERE employee_id = 115;
--ORA-20001: Invalid salary 2800. Salaries for job HR_REP must be between 4000 and 9000
但是,我想知道为什么当我已经在触发器上声明插入时手动插入数据时它不起作用
INSERT INTO employees(first_name, last_name, email, department_id, job_id, hire_date) VALUES ('Lorem', 'Ipsum', 'loremipsum', 30, 'SAL_REP', TRUNC(sysdate));
或者当我使用包裹中的 add_employee 程序时,下面是代码:
PROCEDURE ADD_EMPLOYEE(vfirstname employees.first_name%type,
vlastname employees.last_name%type,
vdepid employees.department_id%type,
vjob employees.job_id%type := 'SA_REP')
IS
vemail employees.email%type;
BEGIN
vemail := upper(substr(vfirstname, 1, 1)) || upper(substr(vlastname,1,7));
IF valid_deptid(vdepid) THEN
INSERT INTO employees(first_name, last_name, email, department_id, job_id, employee_id, hire_date)
VALUES(vfirstname, vlastname, vemail, vdepid, vjob, employees_seq.nextval, TRUNC(SYSDATE));
COMMIT;
ELSE
RAISE_APPLICATION_ERROR(-20003, 'Invalid Department ID');
END IF;
END ADD_EMPLOYEE;
PROCEDURE ADD_EMPLOYEE(vfirstname employees.first_name%type,
vlast_name employees.last_name%type,
vemail employees.email%type,
vjob employees.job_id%type := 'SA_REP',
vmgr employees.manager_id%type := 145,
vsalary employees.salary%type := 1000,
vcomm employees.commission_pct%type := 0,
vdepid employees.department_id%type := 30)
IS
BEGIN
IF valid_deptid(vdepid) THEN
INSERT INTO employees(first_name, last_name, email, department_id, job_id, manager_id, salary, commission_pct, employee_id, hire_date)
VALUES(vfirstname, vlast_name, vemail, vdepid, vjob, vmgr, vsalary, vcomm, employees_seq.nextval, TRUNC(SYSDATE));
COMMIT;
ELSE
RAISE_APPLICATION_ERROR(-20003, 'Invalid Department ID');
END IF;
END ADD_EMPLOYEE;
恭喜你提出了有据可查的问题。
问题出在触发器的 WHEN
子句上。插入时旧值是 NULL
并且在 oracle 中,您不能使用“=”或“!=”与 NULL 进行比较。
检查这个例子:
PDB1--KOEN>create table trigger_test
2 (name VARCHAR2(10));
Table TRIGGER_TEST created.
PDB1--KOEN>CREATE OR REPLACE trigger trigger_test_t1
2 BEFORE INSERT OR UPDATE ON trigger_test
3 FOR EACH ROW
4 WHEN (new.name != old.name)
5 BEGIN
6 RAISE_APPLICATION_ERROR(-20001, 'Some error !');
7 END trigger_test_t1;
8 /
Trigger TRIGGER_TEST_T1 compiled
PDB1--KOEN>INSERT INTO trigger_test (name) values ('koen');
1 row inserted.
PDB1--KOEN>UPDATE trigger_test set name = 'steven';
Error starting at line : 1 in command -
UPDATE trigger_test set name = 'steven'
Error report -
ORA-20001: Some error !
ORA-06512: at "KOEN.TRIGGER_TEST_T1", line 2
ORA-04088: error during execution of trigger 'KOEN.TRIGGER_TEST_T1'
这正是您在代码中看到的行为。插入时触发器似乎没有触发。嗯...不是因为在 oracle 中,'x' != NULL
产生错误。请参阅此答案底部的信息。这是证明。让我们用一个 NVL
函数围绕旧值重新创建触发器。
PDB1--KOEN>CREATE OR REPLACE trigger trigger_test_t1
2 BEFORE INSERT OR UPDATE ON trigger_test
3 FOR EACH ROW
4 WHEN (new.name != NVL(old.name,'x'))
5 -- above is similar to this
6 --WHEN (new.name <> old.name or
7 -- (new.name is null and old.name is not NULL) or
8 -- (new.name is not null and old.name is NULL) )
9 BEGIN
10 RAISE_APPLICATION_ERROR(-20001, 'Some error !');
11 END trigger_test_t1;
12 /
Trigger TRIGGER_TEST_T1 compiled
PDB1--KOEN>INSERT INTO trigger_test (name) values ('jennifer');
Error starting at line : 1 in command -
INSERT INTO trigger_test (name) values ('jennifer')
Error report -
ORA-20001: Some error !
ORA-06512: at "KOEN.TRIGGER_TEST_T1", line 2
ORA-04088: error during execution of trigger 'KOEN.TRIGGER_TEST_T1'
给你。它现在在插入时触发。
现在为什么会这样?根据文档:
因为 null 表示缺少数据,所以 null 不能等于或不等于任何值或另一个 null。但是,Oracle 在计算 DECODE 函数时认为两个空值相等。 查看文档或阅读 20 岁 asktom 上的答案
@KoenLostrie 关于 为什么你的触发器没有在 Insert 上触发是完全正确的。但这只是问题的一半。但是,另一个问题源于同样的误解:NULL 值对 check_salary
的调用通过了 :old.job_id
但它仍然为 null,导致游标(for i in (Select ...)
)在尝试 'WHERE job_id = null`. However there is no exception then a cursor returns no rows, the loop is simply not entered. You need to pass ':new.job_id'。您还希望在 Update 上使用新的作业 ID。图片员工获得晋升更新就像是这样的:
update employee
set job_id = 1011
, salary = 50000.00
where employee = 115;
最后,处理游标充其量是危险的。至少这样做意味着对于给定的 job_id,您允许 Jobs
中有多个行。当这些行具有不同的 min_salary
和 max_salary
时会发生什么您可以更新过程或只在触发器中执行所有操作并删除过程。
create or replace trigger check_salary_trg
before insert or update on employees
for each row
declare
e_invalid_salary_range;
l_job jobs%rowtype;
begin
select *
from jobs
into l_job
where job_id = :new.job_id;
if :new.salary < l_job.min_salary
or :new.salary > l_job.max_salary
then
raise e_invalid_salary_range; ;
end if;
exception
when e_invalid_salary_range then
raise_application_error(-20001, 'Invalid salary ' || psal ||
'. Salaries for job ' || pjobid ||
' must be between ' || l_job.min_salary ||
' and ' || l_job.max_salary
);
end check_salary_trg;
您可以在异常块中添加处理 no_data_found
和 too_many_rows
,但使用约束可以更好地处理这些。
感谢您的精彩解答!感谢他们,我学到了很多。
在检查我的代码尤其是我的触发器时,我错误地将 :old.job_id
传递给了我的 check_salary
过程而不是 :new.job_id
,这就是为什么我一直想知道为什么我的 INSERT即使传递的薪水低于工作的 min_salary
和 max_salary
,它仍在工作。
所以这是我的代码:
check_salary 程序:
CREATE OR REPLACE PROCEDURE check_salary (pjobid employees.job_id%type, psal employees.salary%type)
IS
BEGIN
FOR i in (SELECT min_salary, max_salary
FROM jobs
WHERE job_id = pjobid)
LOOP
IF psal < i.min_salary OR psal > i.max_salary THEN
RAISE_APPLICATION_ERROR(-20001, 'Invalid salary ' || psal || '. Salaries for job ' || pjobid || ' must be between ' || i.min_salary || ' and ' || i.max_salary);
ELSE
DBMS_OUTPUT.PUT_LINE('Salary is okay!');
END IF;
END LOOP;
END check_salary;
/
check_salary_trg:
CREATE OR REPLACE TRIGGER check_salary_trg
BEFORE INSERT OR UPDATE ON employees
FOR EACH ROW
WHEN (new.salary != NVL(old.salary, 0) OR new.job_id != NVL(old.job_id, 'x'))
BEGIN
check_salary(:new.job_id, :new.salary);
END check_salary_trg;
/
以及每当我通过插入和更新传递数据时的结果
INSERT INTO employees(employee_id, first_name, last_name, email, department_id, job_id, hire_date, salary)
VALUES (employees_seq.nextval, 'Lorem', 'Ipsum', 'loremipsum', 30, 'SA_REP', TRUNC(sysdate), 5000);
ORA-20001: Invalid salary 5000. Salaries for job SA_REP must be between 6000 and 12008
UPDATE employees
SET job_id = 'ST_MAN'
WHERE last_name = 'Beh'
ORA-20001: Invalid salary 9000. Salaries for job ST_MAN must be between 5500 and 8500
UPDATE employees
SET salary = 2800
WHERE employee_id = 115;
--1 row(s) updated.
--Salary is okay!
-- will work since min_salary and max_salary of PU_CLERK is 2500 and 5500
我创建了一个触发器来检查是否有人更新了薪水或在员工中插入了新员工 table 使用检查程序,下面是我的程序代码和我的触发器:
CREATE OR REPLACE PROCEDURE check_salary (pjobid employees.job_id%type, psal employees.salary%type)
IS
BEGIN
FOR i in (SELECT min_salary, max_salary
FROM jobs
WHERE job_id = pjobid)
LOOP
IF psal < i.min_salary OR psal > i.max_salary THEN
RAISE_APPLICATION_ERROR(-20001, 'Invalid salary ' || psal || '. Salaries for job ' || pjobid || ' must be between ' || i.min_salary || ' and ' || i.max_salary);
ELSE
DBMS_OUTPUT.PUT_LINE('Salary is okay!');
END IF;
END LOOP;
END check_salary;
CREATE OR REPLACE TRIGGER check_salary_trg
BEFORE INSERT OR UPDATE ON employees
FOR EACH ROW
WHEN (new.salary != old.salary OR new.job_id != old.job_id)
BEGIN
check_salary(:old.job_id, :new.salary);
END check_salary_trg;
现在,当我尝试更新已在 table 上声明的特定员工的工资时,它可以正常工作(结果是评论的那个):
UPDATE employees
SET salary = 2800
WHERE employee_id = 115;
--ORA-20001: Invalid salary 2800. Salaries for job HR_REP must be between 4000 and 9000
但是,我想知道为什么当我已经在触发器上声明插入时手动插入数据时它不起作用
INSERT INTO employees(first_name, last_name, email, department_id, job_id, hire_date) VALUES ('Lorem', 'Ipsum', 'loremipsum', 30, 'SAL_REP', TRUNC(sysdate));
或者当我使用包裹中的 add_employee 程序时,下面是代码:
PROCEDURE ADD_EMPLOYEE(vfirstname employees.first_name%type,
vlastname employees.last_name%type,
vdepid employees.department_id%type,
vjob employees.job_id%type := 'SA_REP')
IS
vemail employees.email%type;
BEGIN
vemail := upper(substr(vfirstname, 1, 1)) || upper(substr(vlastname,1,7));
IF valid_deptid(vdepid) THEN
INSERT INTO employees(first_name, last_name, email, department_id, job_id, employee_id, hire_date)
VALUES(vfirstname, vlastname, vemail, vdepid, vjob, employees_seq.nextval, TRUNC(SYSDATE));
COMMIT;
ELSE
RAISE_APPLICATION_ERROR(-20003, 'Invalid Department ID');
END IF;
END ADD_EMPLOYEE;
PROCEDURE ADD_EMPLOYEE(vfirstname employees.first_name%type,
vlast_name employees.last_name%type,
vemail employees.email%type,
vjob employees.job_id%type := 'SA_REP',
vmgr employees.manager_id%type := 145,
vsalary employees.salary%type := 1000,
vcomm employees.commission_pct%type := 0,
vdepid employees.department_id%type := 30)
IS
BEGIN
IF valid_deptid(vdepid) THEN
INSERT INTO employees(first_name, last_name, email, department_id, job_id, manager_id, salary, commission_pct, employee_id, hire_date)
VALUES(vfirstname, vlast_name, vemail, vdepid, vjob, vmgr, vsalary, vcomm, employees_seq.nextval, TRUNC(SYSDATE));
COMMIT;
ELSE
RAISE_APPLICATION_ERROR(-20003, 'Invalid Department ID');
END IF;
END ADD_EMPLOYEE;
恭喜你提出了有据可查的问题。
问题出在触发器的 WHEN
子句上。插入时旧值是 NULL
并且在 oracle 中,您不能使用“=”或“!=”与 NULL 进行比较。
检查这个例子:
PDB1--KOEN>create table trigger_test
2 (name VARCHAR2(10));
Table TRIGGER_TEST created.
PDB1--KOEN>CREATE OR REPLACE trigger trigger_test_t1
2 BEFORE INSERT OR UPDATE ON trigger_test
3 FOR EACH ROW
4 WHEN (new.name != old.name)
5 BEGIN
6 RAISE_APPLICATION_ERROR(-20001, 'Some error !');
7 END trigger_test_t1;
8 /
Trigger TRIGGER_TEST_T1 compiled
PDB1--KOEN>INSERT INTO trigger_test (name) values ('koen');
1 row inserted.
PDB1--KOEN>UPDATE trigger_test set name = 'steven';
Error starting at line : 1 in command -
UPDATE trigger_test set name = 'steven'
Error report -
ORA-20001: Some error !
ORA-06512: at "KOEN.TRIGGER_TEST_T1", line 2
ORA-04088: error during execution of trigger 'KOEN.TRIGGER_TEST_T1'
这正是您在代码中看到的行为。插入时触发器似乎没有触发。嗯...不是因为在 oracle 中,'x' != NULL
产生错误。请参阅此答案底部的信息。这是证明。让我们用一个 NVL
函数围绕旧值重新创建触发器。
PDB1--KOEN>CREATE OR REPLACE trigger trigger_test_t1
2 BEFORE INSERT OR UPDATE ON trigger_test
3 FOR EACH ROW
4 WHEN (new.name != NVL(old.name,'x'))
5 -- above is similar to this
6 --WHEN (new.name <> old.name or
7 -- (new.name is null and old.name is not NULL) or
8 -- (new.name is not null and old.name is NULL) )
9 BEGIN
10 RAISE_APPLICATION_ERROR(-20001, 'Some error !');
11 END trigger_test_t1;
12 /
Trigger TRIGGER_TEST_T1 compiled
PDB1--KOEN>INSERT INTO trigger_test (name) values ('jennifer');
Error starting at line : 1 in command -
INSERT INTO trigger_test (name) values ('jennifer')
Error report -
ORA-20001: Some error !
ORA-06512: at "KOEN.TRIGGER_TEST_T1", line 2
ORA-04088: error during execution of trigger 'KOEN.TRIGGER_TEST_T1'
给你。它现在在插入时触发。
现在为什么会这样?根据文档: 因为 null 表示缺少数据,所以 null 不能等于或不等于任何值或另一个 null。但是,Oracle 在计算 DECODE 函数时认为两个空值相等。 查看文档或阅读 20 岁 asktom 上的答案
@KoenLostrie 关于 为什么你的触发器没有在 Insert 上触发是完全正确的。但这只是问题的一半。但是,另一个问题源于同样的误解:NULL 值对 check_salary
的调用通过了 :old.job_id
但它仍然为 null,导致游标(for i in (Select ...)
)在尝试 'WHERE job_id = null`. However there is no exception then a cursor returns no rows, the loop is simply not entered. You need to pass ':new.job_id'。您还希望在 Update 上使用新的作业 ID。图片员工获得晋升更新就像是这样的:
update employee
set job_id = 1011
, salary = 50000.00
where employee = 115;
最后,处理游标充其量是危险的。至少这样做意味着对于给定的 job_id,您允许 Jobs
中有多个行。当这些行具有不同的 min_salary
和 max_salary
时会发生什么您可以更新过程或只在触发器中执行所有操作并删除过程。
create or replace trigger check_salary_trg
before insert or update on employees
for each row
declare
e_invalid_salary_range;
l_job jobs%rowtype;
begin
select *
from jobs
into l_job
where job_id = :new.job_id;
if :new.salary < l_job.min_salary
or :new.salary > l_job.max_salary
then
raise e_invalid_salary_range; ;
end if;
exception
when e_invalid_salary_range then
raise_application_error(-20001, 'Invalid salary ' || psal ||
'. Salaries for job ' || pjobid ||
' must be between ' || l_job.min_salary ||
' and ' || l_job.max_salary
);
end check_salary_trg;
您可以在异常块中添加处理 no_data_found
和 too_many_rows
,但使用约束可以更好地处理这些。
感谢您的精彩解答!感谢他们,我学到了很多。
在检查我的代码尤其是我的触发器时,我错误地将 :old.job_id
传递给了我的 check_salary
过程而不是 :new.job_id
,这就是为什么我一直想知道为什么我的 INSERT即使传递的薪水低于工作的 min_salary
和 max_salary
,它仍在工作。
所以这是我的代码:
check_salary 程序:
CREATE OR REPLACE PROCEDURE check_salary (pjobid employees.job_id%type, psal employees.salary%type)
IS
BEGIN
FOR i in (SELECT min_salary, max_salary
FROM jobs
WHERE job_id = pjobid)
LOOP
IF psal < i.min_salary OR psal > i.max_salary THEN
RAISE_APPLICATION_ERROR(-20001, 'Invalid salary ' || psal || '. Salaries for job ' || pjobid || ' must be between ' || i.min_salary || ' and ' || i.max_salary);
ELSE
DBMS_OUTPUT.PUT_LINE('Salary is okay!');
END IF;
END LOOP;
END check_salary;
/
check_salary_trg:
CREATE OR REPLACE TRIGGER check_salary_trg
BEFORE INSERT OR UPDATE ON employees
FOR EACH ROW
WHEN (new.salary != NVL(old.salary, 0) OR new.job_id != NVL(old.job_id, 'x'))
BEGIN
check_salary(:new.job_id, :new.salary);
END check_salary_trg;
/
以及每当我通过插入和更新传递数据时的结果
INSERT INTO employees(employee_id, first_name, last_name, email, department_id, job_id, hire_date, salary)
VALUES (employees_seq.nextval, 'Lorem', 'Ipsum', 'loremipsum', 30, 'SA_REP', TRUNC(sysdate), 5000);
ORA-20001: Invalid salary 5000. Salaries for job SA_REP must be between 6000 and 12008
UPDATE employees
SET job_id = 'ST_MAN'
WHERE last_name = 'Beh'
ORA-20001: Invalid salary 9000. Salaries for job ST_MAN must be between 5500 and 8500
UPDATE employees
SET salary = 2800
WHERE employee_id = 115;
--1 row(s) updated.
--Salary is okay!
-- will work since min_salary and max_salary of PU_CLERK is 2500 and 5500