I have a table called applications and a table called application_history. I want to keep a history of applications and had the idea of using a trigger whenever a row gets updated in the applications table to copy that row to application_history before it is actually updated.
At the moment, I've written this code out from another SO post:
create or replace
trigger APPLICATION_UPDATE_TRG
BEFORE UPDATE ON TBL_APPLICATIONS
FOR EACH ROW
DECLARE
CURSOR curAppHistory IS
SELECT record_number, job_id, submitted_date, status_id, id
FROM tbl_application
WHERE id = :old.id;
vRowAppHistory curAppHistory%ROWTYPE;
BEGIN
OPEN curAppHistory;
FETCH curAppHistory INTO vRowAppHistory;
CLOSE curAppHistory;
INSERT INTO tbl_application_history
(record_number, job_id, submitted_date, status_id, application_id)
VALUES (vRowAppHistory.record_number, vRowAppHistory.job_id, vRowAppHistory.submitted_date,
vRowAppHistory.status_id, vRowAppHistory.id);
END;
However, it's not properly compiling. SQL Developer throws out 3 errors about commands not properly ended and statements being ignored.
What's the proper way to do this?
Edit: The errors:
Error(2,10): PLS-00341: declaration of cursor 'CURAPPHISTORY' is incomplete or malformed
Error(3,5): PL/SQL: SQL Statement ignored
Error(4,12): PL/SQL: ORA-00942: table or view does not exist
Error(6,18): PL/SQL: Item ignored
Error(9,3): PL/SQL: SQL Statement ignored
Error(9,28): PLS-00320: the declaration of the type of this expression is incomplete or malformed
Error(11,3): PL/SQL: SQL Statement ignored
Error(14,29): PLS-00320: the declaration of the type of this expression is incomplete or malformed
Error(14,44): PL/SQL: ORA-00984: column not allowed here
Error(4,12): PL/SQL: ORA-00942: table or view does not exist
A typo? You specified two different tables here.
BEFORE UPDATE ON TBL_APPLICATIONS
FROM tbl_application
Anyway, you'll get ORA-04091 with this trigger. Similar test case:
SYSTEM#dwal> create table t (key number primary key, value varchar2(10));
Table created.
SYSTEM#dwal> insert into t values (1, 'abcdef');
1 row created.
SYSTEM#dwal> insert into t values (2, 'ghijkl');
1 row created.
SYSTEM#dwal> commit;
Commit complete
SYSTEM#dwal> ed
Wrote file S:\\tools\buffer.sql
1 create or replace trigger tt
2 before update on t for each row
3 declare
4 cursor c is
5 select key, value
6 from t
7 where key = :old.key;
8 v c%rowtype;
9 begin
10 open c;
11 fetch c into v;
12 close c;
13 dbms_output.put_line(v.value);
14* end;
09:58:51 SYSTEM#dwal> /
Trigger created.
SYSTEM#dwal> update t set value = '123';
update t set value = '123'
*
ERROR at line 1:
ORA-04091: table SYSTEM.T is mutating, trigger/function may not see it
ORA-06512: at "SYSTEM.TT", line 3
ORA-06512: at "SYSTEM.TT", line 8
ORA-04088: error during execution of trigger 'SYSTEM.TT'
You should probably do it like this:
INSERT INTO tbl_application_history
(record_number, job_id, submitted_date, status_id, application_id)
VALUES
(:old.record_number, :old.job_id, :old.submitted_date, :old.status_id, :old.id);
The whole trigger would be a single insert statement in this case:
SYSTEM#dwal> create table t_log (key number, value varchar2(10));
Table created.
SYSTEM#dwal> ed
Wrote file S:\\tools\buffer.sql
1 create or replace trigger tt
2 before update on t for each row
3 begin
4 insert into t_log values (:old.key, :old.value);
5* end;
SYSTEM#dwal> /
Trigger created.
SYSTEM#dwal> update t set value = '123';
2 rows updated.
SYSTEM#dwal> commit;
Commit complete.
SYSTEM#dwal> select * from t_log;
KEY VALUE
---------- ----------
1 abcdef
2 ghijkl
SYSTEM#dwal> select * from t;
KEY VALUE
---------- ----------
1 123
2 123
Related
CREATE OR REPLACE TRIGGER STUDENT
BEFORE INSERT
ON STUDENT
FOR EACH ROW
BEGIN
IF :NEW.ENO =NULL THEN
RAISE_APPLICATION_ERROR(-21900, 'ENROLL_NO IS MISSING');
END IF;
END;
So this was my trigger definition
But when I try to NUll values its accepted
I'm learning
It is not = null, but is null:
SQL> CREATE OR REPLACE TRIGGER trg_bi_student
2 BEFORE INSERT ON STUDENT
3 FOR EACH ROW
4 BEGIN
5 IF :NEW.ENO is NULL THEN
6 RAISE_APPLICATION_ERROR(-21900, 'ENROLL_NO IS MISSING');
7 END IF;
8 END;
9 /
Trigger created.
SQL> insert into student (eno, name) values (1, 'Little');
1 row created.
SQL> insert into student (eno, name) values (null, 'Foot');
insert into student (eno, name) values (null, 'Foot')
*
ERROR at line 1:
ORA-21000: error number argument to raise_application_error of -21900 is out of
range
ORA-06512: at "SCOTT.TRG_BI_STUDENT", line 3
ORA-04088: error during execution of trigger 'SCOTT.TRG_BI_STUDENT'
SQL>
Though, why trigger? That's a not null constraint (most probably a primary key):
SQL> drop table student;
Table dropped.
SQL> create table student (eno number constraint pk_student primary key,
2 name varchar2(10));
Table created.
SQL> insert into student (eno, name) values (1, 'Little');
1 row created.
SQL> insert into student (eno, name) values (null, 'Foot');
insert into student (eno, name) values (null, 'Foot')
*
ERROR at line 1:
ORA-01400: cannot insert NULL into ("SCOTT"."STUDENT"."ENO")
SQL>
As of your second trigger (posted in a comment): if you show errors, then:
SQL> CREATE OR REPLACE TRIGGER TOTALMARKS
2 BEFORE INSERT ON STUDENT
3 FOR EACH ROW
4 BEGIN
5 :NEW.TOTAL: =:NEW.S1+:NEW.S2+:NEW.S3;
6 DBMS_OUTPUT.PUT_LINE('TOTAL="| | :NEW. TOTAL) ;
7 END;
8 /
Warning: Trigger created with compilation errors.
SQL> show err
Errors for TRIGGER TOTALMARKS:
LINE/COL ERROR
-------- -----------------------------------------------------------------
2/11 PLS-00103: Encountered the symbol " " when expecting one of the
following:
:= . ( # % ; indicator
SQL>
This is the 2nd line in excecutable part of the trigger:
:NEW.TOTAL: =:NEW.S1+:NEW.S2+:NEW.S3;
1234567890123456
^
|
11th character
It says that you should've used := and not : = (i.e. no space in between), but then you get another error:
SQL> CREATE OR REPLACE TRIGGER TOTALMARKS
2 BEFORE INSERT ON STUDENT
3 FOR EACH ROW
4 BEGIN
5 :NEW.TOTAL :=:NEW.S1+:NEW.S2+:NEW.S3;
6 DBMS_OUTPUT.PUT_LINE('TOTAL="| | :NEW. TOTAL) ;
7 END;
8 /
Warning: Trigger created with compilation errors.
SQL> show err
Errors for TRIGGER TOTALMARKS:
LINE/COL ERROR
-------- -----------------------------------------------------------------
3/22 PLS-00103: Encountered the symbol "TOTAL="| | :NEW. TOTAL) ;
END;" when expecting one of the following: (...)
You can't enclose strings into double quotes - use single ones. But, there's another error (consecutive pipe sign for concatenation), and yet another (no space between :new and .total), until finally it compiles:
SQL> CREATE OR REPLACE TRIGGER TOTALMARKS
2 BEFORE INSERT ON STUDENT
3 FOR EACH ROW
4 BEGIN
5 :NEW.TOTAL :=:NEW.S1+:NEW.S2+:NEW.S3;
6 DBMS_OUTPUT.PUT_LINE('TOTAL='|| :NEW.TOTAL) ;
7 END;
8 /
Trigger created.
SQL>
I try to insert a new record in my database using pl/sql, so first of all I generate a new sequence like so:
select my_seq.nextval into seqId from dual;
And then i try to insert a new record using the generated seqId like this :
insert into myTable (id) values seqId ;
But when an error occurred during the insertion I want to decrement my sequence in an exception block. Does anyone have an idea please?
As you were already told, you shouldn't be doing this at all. Anyway, here's how you might do it.
Sample table and a sequence:
SQL> create table mytable (id number primary key);
Table created.
SQL> create sequence seq;
Sequence created.
SQL> set serveroutput on
Procedure which inserts seq.nextval into mytable and decrements the sequence in a case of a failure. I'm doing it in a simplest way - dropping it and recreating with the start parameter set to the last fetched value minus 1. DBMS_OUTPUT calls are here just to show what's going on in the procedure.
SQL> create or replace procedure p_test as
2 seqid number;
3 begin
4 seqid := seq.nextval;
5 dbms_output.put_line('Sequence number to be inserted = ' || seqid);
6
7 insert into mytable(id) values (seqid);
8
9 exception
10 when others then
11 dbms_output.put_line(sqlerrm);
12 execute immediate 'drop sequence seq';
13 execute immediate 'create sequence seq start with ' || to_char(seqid - 1);
14 end;
15 /
Procedure created.
Let's test it: this should insert 1:
SQL> exec p_test;
Sequence number to be inserted = 1
PL/SQL procedure successfully completed.
SQL> select * from mytable;
ID
----------
1
So far so good. Now, I'll manually insert ID = 2 so that the next procedure call violates unique constraint:
SQL> insert into mytable values (2);
1 row created.
Calling the procedure again:
SQL> exec p_test;
Sequence number to be inserted = 2
ORA-00001: unique constraint (SCOTT.SYS_C007547) violated
PL/SQL procedure successfully completed.
OK; procedure silently completed. It didn't insert anything, but it decremented the sequence:
SQL> select * from mytable;
ID
----------
1 --> populated with the first P_TEST call
2 --> populated manually
SQL> select seq.nextval from dual;
NEXTVAL
----------
1 --> sequence is decremented from 2 to 1
If I delete the offending ID = 2 and try again:
SQL> delete from mytable where id = 2;
1 row deleted.
SQL> exec p_test;
Sequence number to be inserted = 2
PL/SQL procedure successfully completed.
SQL> select * from mytable;
ID
----------
1
2
SQL> select seq.nextval from dual;
NEXTVAL
----------
3
SQL>
Right; kind of works, but it's not worth the pain.
Besides, as you commented that there are 20 rows in a table. What will you do if someone deletes row #13. Will you decrement all values between #14 and 20? What if that's a primary key, referenced by some foreign keys?
Seriously, don't do it.
You can not decrement after the sequence is incremented (A sequence is either incremented or decremented not both) or reinitialize the sequence (start with ...) in Oracle. There is no way of doing that. Hope this helps.
However, if you want to continue this absurdity you can try this but you need to initialize your sequence first which is myseq.nextval;
then you can first try to insert currval and if succeeds then you can increment your sequence otherwise sequence will have its previous value.
Declare
currval pls_integer;
inc pls_integer;
Begin
select seq.currval into currval from dual;
insert into myTable (id) values (currval) ;
select seq.nextval into inc from dual;
Exception
when others then
do the exception handling;
end;
I am trying to create a trigger in oracle that ensures that the value of ExpDate from the table ExpIt is less than or equal to the ERSubDate from the ExpReport on INSERT and UPDATE statements that change the ExpDate in the ExpIt table.
When ran in the command prompt, the following comes up
warning: Trigger created with compilation errors.
Here is what I have tried so far, where am I going wrong?
Thank you in advance.
CREATE OR REPLACE TRIGGER Expense_Date
BEFORE INSERT OR UPDATE OF ExpDate
ON ExpIt
FOR EACH ROW
DECLARE
anExpDate ExpIts.ExpDate%TYPE;
anERSubDate ExpReport.ERSubDate%TYPE;
DateError EXCEPTION;
ExMessage VARCHAR(200);
BEGIN
SELECT ExpDate, ERSubDate
INTO anExpDate, anERSubDate
FROM ExpIt, ExpReport
WHERE ExpIt.ExpDate = :NEW.ExpDate;
IF anExpDate <= anERSubDate THEN
RAISE DateError;
END IF;
EXCEPTION
WHEN DateError THEN
ExMessage := ExMessage || 'Expense Date is Incorrect as it is after the Expense Report Submition date' ||
to_date(anExpDate);
raise_application_error(-20001, ExMessage);
END;
/
Before you go too far down this track - be aware that you generally cannot access the table you are triggering on from within the trigger itself.
In your case, your trigger is on EXPIT and you want to query EXPIT. That won't work.
Here's a trivial example of that:
SQL> create table t (x int );
Table created.
SQL> insert into t values (1);
1 row created.
SQL> commit;
Commit complete.
SQL>
SQL> create or replace
2 trigger TRG
3 before insert on T
4 for each row
5 declare
6 blah int;
7 begin
8 select count(*) into blah from t;
9 end;
10 /
Trigger created.
SQL>
SQL> insert into t values (2);
1 row created.
It looks fine, but in reality, there are plenty of cases where it will NOT work
SQL> insert into t
2 select rownum from dual
3 connect by level <= 5;
insert into t
*
ERROR at line 1:
ORA-04091: table MCDONAC.T is mutating, trigger/function may not see it
ORA-06512: at "MCDONAC.TRG", line 4
ORA-04088: error during execution of trigger 'MCDONAC.TRG'
This is a big topic, and more details on the issue and how to work around it are here
https://asktom.oracle.com/pls/apex/asktom.search?file=MutatingTable.html#presentation-downloads-reg
I have a table named TABLE_1 which has 3 columns
row_id row_name row_descr
1 check1 checks here
2 check2 checks there
These rows are created through a front end application. Now suppose I delete the entry with row_name check2 from the front end and create another entry from front end with row_name check3, in database my entries will be as follows.
row_id row_name row_descr
1 check1 checks here
3 check3 checks
Now row_id if you observe is not a normal one time increment, Now my problem is i'm writing an insert statement to automate something and i don't know what i should insert in the row_id column. Previously i thought it is just new row_id = old row_id +1. But this is not the case here. Please help
EDIT :
Currently im inserting like this which is Wrong :
insert into TABLE1 (row_id, row_name, row_descr
) values ( (select max (row_id) + 1 from TABLE1),'check1','checks here');
row_id is not a normal one time increment.
Never ever calculate ids by max(id)+1 unless you can absolutly exclude simultaneous actions ( which is almost never ever the case). In oracle (pre version 12 see Kumars answer) create a sequence once and insert the values from that sequences afterwards.
create sequence my_sequence;
Either by a trigger which means you don't have to care about the ids during the insert at all:
CREATE OR REPLACE TRIGGER myTrigger
BEFORE INSERT ON TABLE1 FOR EACH ROW
BEGIN
SELECT my_sequence.NEXTVAL INTO :NEW.row_id FROM DUAL;
END;
/
Or directly with the insert
insert into TABLE1 (row_id, row_name, row_descr
) values ( my_sequence.nextval,'check1','checks here');
Besides using row_id as column name in oracle might be a little confusing, because of the pseudocolumn rowid which has a special meaning.
To anwser your quetstion though: If you really need to catch oracle errors as excpetions you can do this with PRAGMA EXCEPTION INIT by using a procedure for your inserts. It might look somehow like this:
CREATE OR REPLACE PROCEDURE myInsert( [...] )
IS
value_allready_exists EXCEPTION;
PRAGMA EXCEPTION_INIT ( value_allready_exists, -00001 );
--ORA-00001: unique constraint violated
BEGIN
/*
* Do your Insert here
*/
EXCEPTION
WHEN value_allready_exists THEN
/*
* Do what you think is necessary on your ORA-00001 here
*/
END myInsert;
Oracle 12c introduced IDENTITY columns. Precisely, Release 12.1. It is very handy with situations where you need to have a sequence for your primary key column.
For example,
SQL> DROP TABLE identity_tab PURGE;
Table dropped.
SQL>
SQL> CREATE TABLE identity_tab (
2 ID NUMBER GENERATED ALWAYS AS IDENTITY,
3 text VARCHAR2(10)
4 );
Table created.
SQL>
SQL> INSERT INTO identity_tab (text) VALUES ('Text');
1 row created.
SQL> DELETE FROM identity_tab WHERE ID = 1;
1 row deleted.
SQL> INSERT INTO identity_tab (text) VALUES ('Text');
1 row created.
SQL> INSERT INTO identity_tab (text) VALUES ('Text');
1 row created.
SQL> INSERT INTO identity_tab (text) VALUES ('Text');
1 row created.
SQL> DELETE FROM identity_tab WHERE ID = 2;
1 row deleted.
SQL> SELECT * FROM identity_tab;
ID TEXT
---------- ----------
3 Text
4 Text
SQL>
Now let's see what's under the hood -
SQL> SELECT table_name,
2 column_name,
3 generation_type,
4 identity_options
5 FROM all_tab_identity_cols
6 WHERE owner = 'LALIT'
7 /
TABLE_NAME COLUMN_NAME GENERATION IDENTITY_OPTIONS
-------------------- --------------- ---------- --------------------------------------------------
IDENTITY_TAB ID ALWAYS START WITH: 1, INCREMENT BY: 1, MAX_VALUE: 9999999
999999999999999999999, MIN_VALUE: 1, CYCLE_FLAG: N
, CACHE_SIZE: 20, ORDER_FLAG: N
SQL>
So, there you go. A sequence implicitly created by Oracle.
And don't forget, you can get rid off the sequence only with the purge option with table drop.
If you are not worried about which values are causing the error, then you could handle it by including a /*+ hint */ in the insert statement.
Here is an example where we would be selecting from another table, or perhaps an inner query, and inserting the results into a table called TABLE_NAME which has a unique constraint on a column called IDX_COL_NAME.
INSERT /*+ ignore_row_on_dupkey_index(TABLE_NAME(IDX_COL_NAME)) */
INTO TABLE_NAME(
INDEX_COL_NAME
, col_1
, col_2
, col_3
, ...
, col_n)
SELECT
INDEX_COL_NAME
, col_1
, col_2
, col_3
, ...
, col_n);
Oracle will blow past the redundant row. This is not a great solution if you care about know WHICH row is causing the issue, or anything else. But if you don't care about that and are fine just keeping the first value that was inserted, then this should do the job.
You can use an exception build in which will raise whenever there will be duplication on unique key
DECLARE
emp_count number;
BEGIN
select count(*) into emp_count from emp;
if emp_count < 1 then
insert into emp
values(1, 'First', 'CLERK', '7839', SYSDATE, 1200, null, 30);
dbms_output.put_line('Clerk added');
else
dbms_output.put_line('No data added');
end if;
EXCEPTION
when dup_val_on_index then
dbms_output.put_line('Tried to add row with duplicated index');
END;
I want to use an if statement inside trigger but the value if comparison will come from an other select statement.
I have done the following:
create or replace
Trigger MYTRIGGER
After Insert On Table1
Referencing Old As "OLD" New As "NEW"
For Each Row
Begin
Declare Counter Int;
Select Count(*) From Table2 Where Table2."Email" = :New.U_MAIL Into Counter;
IF Counter < 1 THEN
//INSERT Statement here...
END IF;
End;
My logic is simple, if same email user exists, insert will not work.
Above code did not work. How can we do this?
A few syntax errors. Would be closer to something like this:
create or replace
Trigger MYTRIGGER
After Insert On Table1
Referencing Old As "OLD" New As "NEW"
For Each Row
DECLARE
v_count NUMBER;
BEGIN
SELECT COUNT(*)
INTO v_count
FROM Table2
WHERE Email = :New.U_MAIL
;
IF v_count > 0
THEN
RAISE_APPLICATION_ERROR(-20000, 'Not inserted...');
END IF;
END;
Your approach is wrong. Referential integrity should not be made using triggers, it just cannot work as required. See example:
Connected to Oracle Database 12c Enterprise Edition Release 12.1.0.2.0
Connected as test#soft12c1
SQL> create table mail_1 (email varchar2(100));
Table created
SQL> create table mail_2 (email varchar2(100));
Table created
SQL> create trigger mail_1_check
2 before insert on mail_1
3 for each row
4 declare
5 cnt integer;
6 begin
7 select count(*) into cnt from mail_2 where email = :new.email;
8 if cnt > 0 then
9 raise_application_error(-20100, 'Email already exists');
10 end if;
11 end;
12 /
Trigger created
SQL> insert into mail_2 values ('president#gov.us');
1 row inserted
SQL> insert into mail_1 values ('king#kingdom.en');
1 row inserted
SQL> insert into mail_1 values ('president#gov.us');
ORA-20100: Email already exists
ORA-06512: at "TEST.MAIL_1_CHECK", line 6
ORA-04088: error during execution of trigger 'TEST.MAIL_1_CHECK'
It looks like trigger works right, but it's not true. See what happens when several users will works simultaneously.
-- First user in his session
SQL> insert into mail_2 values ('dictator#country.by');
1 row inserted
-- Second user in his session
SQL> insert into mail_1 values ('dictator#country.by');
1 row inserted
-- First user is his session
SQL> commit;
Commit complete
-- Second user is his session
SQL> commit;
Commit complete
-- Any user in any session
SQL> select * from mail_1 natural join mail_2;
EMAIL
--------------------------------------------------------------------------------
dictator#country.by
If using triggers for this task, you should serialize any attempts to use this data, say, execute LOCK TABLE IN EXCLUSIVE MODE unless commit. Generally it's a bad decision. For this concrete task you can use much better approach:
Connected to Oracle Database 12c Enterprise Edition Release 12.1.0.2.0
Connected as test#soft12c1
SQL> create table mail_1_2nd(email varchar2(100));
Table created
SQL> create table mail_2_2nd(email varchar2(100));
Table created
SQL> create materialized view mail_check
2 refresh complete on commit
3 as
4 select 1/0 data from mail_1_2nd natural join mail_2_2nd;
Materialized view created
OK. Let's see, what if we try to use same email:
-- First user in his session
SQL> insert into mail_1_2nd values ('dictator#gov.us');
1 row inserted
-- Second user in his session
SQL> insert into mail_2_2nd values ('dictator#gov.us');
1 row inserted
SQL> commit;
Commit complete
-- First user in his session
SQL> commit;
ORA-12008: error in materialized view refresh path
ORA-01476: divisor is equal to zero
SQL> select * from mail_1_2nd natural join mail_2_2nd;
EMAIL
--------------------------------------------------------------------------------
no rows selected