I have a table EMP in my apex oracle database that contains an attribute of salary named sal. I have another table EMPSAL that has 3 attributes named averageSal, minSal, maxSal which are to be updated using triggers whenever any DML operation is performed on the EMP table's sal column. Here is the trigger used for upgrading:
create or replace trigger empsal_update_trigger
AFTER update on emp
for each row
declare
avgSal2 emp.sal%type;
minSal2 emp.sal%type;
maxSal2 emp.sal%type;
begin
select avg(sal), min(sal), max(sal) into avgSal2, minSal2, maxSal2 from emp;
delete from empsal;
insert into empsal values(avgSal2, minSal2, maxSal2);
end;
The insert and delete triggers work fine, but the update one given above gives error whenever A record is updated in EMPSAL. I have tried using before keyword instead of after but it's no use.
You don't need a row level trigger for this case, but use a statement level one. Even no need to use local variable definition through use of INSERT INTO ... SELECT... statement.
So, just remove FOR EACH ROW such as
CREATE OR REPLACE TRIGGER empsal_update_trigger AFTER UPDATE ON emp
BEGIN
DELETE empsal;
INSERT INTO empsal
SELECT AVG(sal), MIN(sal), MAX(sal)
FROM emp;
END;
/
Related
Create trigger that will
not allow update of salary for employee
who are in sales department.
Emp(no,name,dno,salary,designamtion)
Dept(dno,name,location)
Errors: TRIGGER MYTRIGGER1
Line/Col: 6/11 PLS-00049: bad bind variable 'OLD.EMP'
Line/Col: 6/31 PLS-00049: bad bind variable 'OLD.EMP'
DROP TABLE EMP;
DROP TABLE DEPT;
CREATE TABLE Dept
(
dno NUMBER PRIMARY KEY,
name VARCHAR(15),
location VARCHAR(15) );
CREATE TABLE Emp
(
no NUMBER PRIMARY KEY,
name VARCHAR(15),
dno NUMBER,
salary NUMBER,
designamtion VARCHAR(15),
FOREIGN KEY(dno) REFERENCES Dept(dno) );
insert into DEPT values (1,'SALES','GUJARAT');
insert into DEPT values (2,'MARKETING','UP');
insert into DEPT values (3,'MANUFACTURING','MP');
insert into DEPT values (4,'DEALING','VAPI');
insert into DEPT values (5,'SELL','TAPI');
insert into EMP values (1,'AMAN',2,45400,'MANAGER');
insert into EMP values (2,'BHAMAN',5,20000,'GM');
insert into EMP values (3,'CHAMAN',3,34400,'ADVISOR');
insert into EMP values (4,'DAMAN',4,75400,'WORKER');
insert into EMP values (5,'KHAMAN',1,42400,'MANAGER');
CREATE OR REPLACE trigger MYTRIGGER1
BEFORE UPDATE OF SALARY ON EMP
for each row
declare
hmmm VARCHAR(15);
begin
select Dept.name into hmmm
from Dept, Emp
where :old.emp.no=no and :old.emp.dno=Dept.dno;
dbms_output.put_line(hmmm);
end;
You don't need to alias the table the trigger is on. This will compile fine:
CREATE OR REPLACE trigger MYTRIGGER1
BEFORE UPDATE OF SALARY ON EMP
for each row
declare
hmmm VARCHAR(15);
begin
select Dept.name into hmmm
from Dept, Emp
where :old.no=no and :old.dno=Dept.dno;
dbms_output.put_line(hmmm);
end;
However, this trigger will fail when you update the table. You cannot select from the table itself in the trigger body. This will raise a mutating table error. Since it is an assignment I'll leave the research up to you.
As Koen said, your code will suffer from mutating table error.
Lucky you - you don't have to select from the table you're just updating (the emp table, right?) - you have everything you need in :new.dno. So:
SQL> create or replace trigger mytrigger1
2 before update of salary on emp
3 for each row
4 declare
5 hmmm varchar2(15);
6 begin
7 select d.name
8 into hmmm
9 from dept d
10 where d.dno = :new.dno;
11
12 dbms_output.put_line('Department name = ' || hmmm);
13 end;
14 /
Trigger created.
Testing:
SQL> set serveroutput on
SQL>
SQL> update emp set salary = 1000 where no = 1;
Department name = MARKETING
1 row updated.
SQL> update emp set salary = 1000;
Department name = MARKETING
Department name = SELL
Department name = MANUFACTURING
Department name = DEALING
Department name = SALES
5 rows updated.
SQL>
Your request is a trigger that disallows certain action for employees in Sales. Your trigger would not do so even it compiled. Two problems:
dbms_output does not prevent anything it merely 'prints' a message.
it prevents any update to salary, except when emp.dno or emp.no is also updated.
First (getting up on soapbox). You are enforcing a business rule. This should not be done in a trigger. It should be done in either the business rules engine of your application or in a database constraint or if you want to be super cautions both. Now a trigger will do the enforcement but business rule violations should glaringly obvious and easy to find, in a trigger becomes a side effect of an action, hidden away and difficult to find. (OK enough of that getting off soapbox).
The prevention method your looking for is raise_application_error. And you want a little code as possible in a trigger. In this case you have everything needed from the EMP table through the pesudo rows :old and :new so there is no reason to join your tables. However you do need to look at the specific department. It is too bad Oracle constrains the Exists predicate to introduction of a sub-select, this would be a perfect place for the structure:
If EXISTS (selects ...) then ...
But that is not available. The following uses a technique of reversing the requirement. That is it selects what is NOT wanted then if found it raise the exception, and if not found it suppress the Oracle raised error. So:
create or replace trigger mytrigger1
before update of salary on emp
for each row
declare
hmmm varchar2(01);
begin
select null
into hmmm
from dept d
where d.dno = :old.dno
and d.name = 'SALES';
raise_application_error(-20199, 'Can not update salary for Employee in Sales.');
exception
when no_data_found then null;
end mytrigger1;
This is sometimes referred to as "Programming the Exception" and is not a generally recommended procedure but at times it is useful. See fiddle here.
There is an outstanding question not addressed here. Can an employee in Sales have a salary change if they are also transferring departments. I.e. should the following succeed or fail?
update emp e
set dno = 2
, salary = salary + 5000
where e.no = 5;
I want to create a trigger that will store the timing of when a new row is inserted into a my employee's table.
so far this is my code, but i keep getting errors.
trigger:
CREATE OR REPLACE TRIGGER insert_new_employee
AFTER INSERT ON employees
BEGIN
insert into audit_cheker (date_create) values (sysdate());
END;
my table to store the timing for when inserting a new element:
create table audit_cheker(
date_create date
)
the error I've received is
00000 - "trigger '%s.%s' is invalid and failed re-validation"
Cause:
A trigger was attempted to be retrieved for execution and was
found to be invalid. This also means that compilation/authorization
failed for the trigger.
Action:
Options are to resolve the compilation/authorization errors,
disable the trigger, or drop the trigger.
Based on information you posted so far, it should work. Here's a test case:
SQL> create table audit_cheker
2 (date_create date);
Table created.
SQL> create table employees as select empno, ename, deptno From emp;
Table created.
Trigger:
SQL> CREATE OR REPLACE TRIGGER insert_new_employee
2 AFTER INSERT ON employees
3 BEGIN
4 insert into audit_cheker (date_create) values (sysdate());
5 END;
6 /
Trigger created.
No problems so far. Let's test it:
SQL> insert into employees (empno, ename, deptno) values (1, 'LF', 10);
1 row created.
SQL> select * from employees where empno = 1;
EMPNO ENAME DEPTNO
---------- ---------- ----------
1 LF 10
SQL> select * From audit_cheker;
DATE_CREATE
----------------
2020-04-24 07:18
As you can see, it works.
If trigger is invalid, try to recompile it:
SQL> alter trigger insert_new_employee compile;
Trigger altered.
If it still doesn't help, please, edit your initial message (the question you posted) and copy/paste your SQL*Plus session so that we could see what you did and how Oracle responded.
I have emp table in schema1 and emp_fianl in schema2.
emp
empid ename estatus
1 abc incomplete
2 xyz complete
3 ifg incomplete
4 mno incomplete
Emp_final
empid ename estatus
2 xyz complete
I have to create a trigger to insert data in Schema2 emp_final table when the estatus in schema1 emp table changes to complete.
I have written below trigger for the same:
Create or replace trigger tri_emp_final
After update on emp
BEGIN
IF :new.estatus='complete' then
Insert into emp_final
(select :old.empid,:old.ename,:new.estatus from schem1.emp);
END IF;
END;
/
I am getting mutating error message for the above code. When I am trying to update the status in emp table. I am a java developer and do not have much experience in Oracle, SQL. Can anyone please help?
First thing: we you want to use :old and :new, your trigger MUST be FOR EACH ROW. So you need to change : before update on emp for each row.
Second: like Goran Stefanović wrote, you don't make that select to insert, just use the :old values.
Use
Create or replace trigger tri_emp_final
After update on emp for each row
BEGIN
IF :new.estatus='complete' then
Insert into emp_final ( empid , ename , estatus)
Values
(:old.empid,:old.ename,:new.estatus );
END IF;
END;
/
You can find a working demo here
I'm new to pl/sql and grappling with triggers. I am required to use a trigger for this code. I have 2 tables, job (job_id, job_name, job_price) and job_history (job_id, oldprice, datechanged). I'm trying to create a trigger that adds the old job details to the job_history table when the job_price field in the job table is updated either if no row already exist or if the new job price for that job id is more than any previously stored prices for that job id in the job_history table. The job id field in the job table cannot have duplicates but the job id field in the job_history table can have duplicates. Further, if the condition is not met, that is, the new job price is less than all previously stored prices for that job id, then the error should be trapped.
I've tried this code:
CREATE OR REPLACE TRIGGER conditional_update_job_hist
AFTER UPDATE OF jbsprice ON job
FOR EACH ROW
WHEN (new.jbsprice)<min(old.jbsprice);
BEGIN
INSERT INTO job_history (jbsid, oldprice) VALUES (:old.jbsid,:old.jbsprice);
IF :new.price is<>min(oldprice) THEN
RAISE_APPLICATION_ERROR('Condition not met.');
ENDIF;
END;
/
This resulted in an error at line 4
ORA-00920: invalid relational operator.
I've checked the oracle online documentation. It's confusing. Do I need to use a cursor and loop inside the trigger? The less than operator looks okay and the min(function) looks okay. I cannot see where I'm going wrong. Please help.
at a first glance, I would suggest the following:
CREATE OR REPLACE TRIGGER conditional_update_job_hist
AFTER UPDATE OF jbsprice ON job
FOR EACH ROW
DECLARE
hist_exists number;
BEGIN
hist_exists := 0;
begin
-- select 1 if there is an entry in job_history of that jbsid
-- and an oldprice exists which is more than new jbsprice
select distinct 1
into hist_exists
from job_history
where jbsid = :old.jbsid
and oldprice > :new.jbsprice;
exception when no_data_found then hist_exists := 0;
end;
IF hist_exists = 0 then
INSERT INTO job_history (jbsid, oldprice) VALUES (:old.jbsid,:old.jbsprice);
END IF;
END;
/
Ignoring :
CREATE OR REPLACE TRIGGER conditional_update_job_hist
AFTER UPDATE OF jbsprice ON job
FOR EACH ROW
WHEN (:new.jbsprice)<min(:old.jbsprice);
BEGIN
INSERT INTO job_history (jbsid, oldprice) VALUES (:old.jbsid,:old.jbsprice);
IF :new.price is<>min(oldprice) THEN
RAISE_APPLICATION_ERROR('Condition not met.');
ENDIF;
END;
Firstly, I am quite new in the oracle audit trail thing. So, I have to audit the actions of a particular user.
My issue is that when I check the dba_fga_audit_trail, I see in the sql_text column that the user ran a delete statement, but I do not know what it is that he deleted. This is what is written in the cell from the sql_text column:
DELETE FROM USR WHERE USR_ID = :B
Could you please help me find a way to store in the audit trail what line was deleted from a particular table?
You need to create a table to hold the deleted data, which will have one row per deletion. Then, create a trigger on the USR table which will run after delete. This trigger will select the data you want to audit from the 'dummy' :OLD table, and insert it into your audit table.
My issue is that when I check the dba_fga_audit_trail, I see in the sql_text column that the user ran a delete statement, but I do not know what it is that he deleted.
You need to set audit_trail to DBMS_FGA.DB + DBMS_FGA.EXTENDED sends the audit trail to the SYS.FGA_LOG$ table in the database and includes SQL Text and SQL Bind.
For example,
SELECT * FROM emp WHERE empno = 9998;
INSERT INTO emp (empno, ename, sal) VALUES (9998, 'Bill', 1);
UPDATE emp SET sal = 10 WHERE empno = 9998;
DELETE emp WHERE empno = 9998;
ROLLBACK;
-- Check the audit trail.
CONN sys/password AS SYSDBA
SELECT sql_text FROM dba_fga_audit_trail;
SQL_TEXT
--------------------------------------
SELECT * FROM emp WHERE empno = 9998
INSERT INTO emp (empno, ename, sal) VALUES (9998, 'Bill', 1)
UPDATE emp SET sal = 10 WHERE empno = 9998
DELETE emp WHERE empno = 9998
4 rows selected.
You can also have a look at Tim Hall's demo.