Insert value on second trigger with value from first trigger PL\SQL - sql

Hi I try to Insert value in the second trigger with new id from first trigger only if condition is fulfiled, but I'm stuck.
table1_trg works
CREATE TABLE table1 (
id NUMBER(9,0) NOT NULL,
subject VARCHAR2(200) NOT NULL,
url_address VARCHAR2(200) NOT NULL,
)
CREATE OR REPLACE TRIGGER table1_trg
BEFORE INSERT ON table1
FOR EACH ROW
BEGIN
SELECT table1_seq.NEXTVAL
INTO :new.id
FROM dual;
END;
/
CREATE OR REPLACE TRIGGER table1_url
BEFORE INSERT ON table1
FOR EACH ROW
WHEN (NEW.subject = 'Task')
BEGIN
INSERT INTO CSB.table1 (url_address)
VALUES ('blabla.com?' || :new.id);
END;
/
I insert only subject but after that i receive exception that subject can not be null.
INSERT INTO corp_tasks_spec (subject) VALUES ('Task')
Any ideas how to resolve it?

You should not be inserting a new record into the same table, you should be modifying the column values for the row you're already inserting - which the trigger is firing against. You're getting the error because of that second insert - which is only specifying the URL value, not the subject or ID (though the first trigger would fire again and set the ID for that new row as well - so it complains about the subject).
Having two triggers on the same firing point can be difficult in old versions of Oracle as the order they fired wasn't guaranteed - so for instance your second trigger might fire before the first, and ID hasn't been set yet. You can control the order in later versions (from 11g) with FOLLOWS:
CREATE OR REPLACE TRIGGER table1_url
BEFORE INSERT ON table1
FOR EACH ROW
FOLLOWS table1_trg
WHEN (NEW.subject = 'Task')
BEGIN
:NEW.url_address := 'blabla.com?' || :new.id;
END;
/
This now fires after the first trigger, so ID is set, and assigns a value to the URL in this row rather than trying to create another row:
INSERT INTO table1 (subject) VALUES ('Task');
1 row inserted.
SELECT * FROM table1;
ID SUBJECT URL_ADDRESS
---------- ---------- --------------------
2 Task blabla.com?2
But you don't really need two triggers here, you could do:
DROP TRIGGER table1_url;
CREATE OR REPLACE TRIGGER table1_trg
BEFORE INSERT ON table1
FOR EACH ROW
BEGIN
:NEW.id := table1_seq.NEXTVAL; -- no need to select from dual in recent versions
IF :NEW.subject = 'Task' THEN
:NEW.url_address := 'blabla.com?' || :new.id;
END IF;
END;
/
Then that trigger generates the ID and sets the URL:
INSERT INTO table1 (subject) VALUES ('Task');
1 row inserted.
SELECT * FROM table1;
ID SUBJECT URL_ADDRESS
---------- ---------- --------------------
2 Task blabla.com?2
3 Task blabla.com?3
Of course, for anything except Task you'll have to specify the URL as part of the insert, or it will error as that is a not-null column.

Create sequence
CREATE SEQUENCE table1_SEQ
START WITH 1
MAXVALUE 100000
MINVALUE 1
NOCYCLE
NOCACHE
NOORDER;
CREATE TRIGGER
CREATE OR REPLACE TRIGGER table1_TRG
Before Insert
ON table1 Referencing New As New Old As Old
For Each Row
Declare V_Val Number;
Begin
Select table1_SEQ.NextVal into V_Val From Dual;
If Inserting Then
:New.id:= V_Val;
End If;
End;
/

Related

Is there any way we can emulate the way of using :old and :new of row triggers in statement triggers in PL/SQL

Good Day Buddies!
So, here is my Question, it says -
Write a update, delete trigger on clientmstr table. The System
should keep track of the records that ARE BEING updated or
deleted. The old value of updated or deleted records should be
added in audit_trade table. (Separate implementation using both row
and statement triggers)
And my solution looks like this -
-- For row trigger
create or replace trigger row_trigger
before delete or update on client_master
referencing old as old new as new
for each row
begin
insert into audit_table values(
:old.client_id, :old.client_name, :old.client_budget
);
end;
/
And as per the question I have to implement the same using statement trigger but I couldn't think of a way it can be done. I studied about statement triggers and I learned that we can't use :old and :new here. Is there any way we can implement the same row trigger method of adding in audit table using statement trigger? I am just starting out and it's just been two days I started learning PL/SQL. I spend whole day searching everywhere on the internet - tried looking for an example but I am not getting it. Can anyone help?
Edit
(1) I am using Oracle SQL Developer
(2) As someone suggested in comments - it isn't possible to do this in statement trigger, I think the same. I have to submit my assignment this Saturday. I had a conversation with my teacher - she said it's possible to implement it using statement trigger. I asked her how - but she didn't responded. Then I asked her for a hint and she said this (I'm copy pasting her text)-
Create a separate table with col as operations and timestamp. Write statement level trigger on insert update and delete operations. The trigger will capture the operation fired and timestamp by inserting values in table.
I am not getting what does that mean or how to do it! Can anyone help me solve this?
You could use a compound trigger.
Create the types:
CREATE TYPE client_master_obj IS OBJECT(
id NUMBER,
name VARCHAR2(20),
budget NUMBER(10,2)
);
CREATE TYPE client_master_table IS TABLE OF client_master_obj;
Then the trigger:
CREATE TRIGGER client_master_cmp_trigger
FOR DELETE OR UPDATE ON client_master
COMPOUND TRIGGER
data client_master_table := client_master_table();
AFTER EACH ROW
IS
BEGIN
data.EXTEND(1);
data(data.COUNT) := client_master_obj(
:OLD.client_id,
:OLD.client_name,
:OLD.client_budget
);
END AFTER EACH ROW;
AFTER STATEMENT
IS
BEGIN
INSERT INTO audit_table (client_id, client_name, client_budget, trg_type)
SELECT id,
name,
budget,
'C'
FROM TABLE(data);
END AFTER STATEMENT;
END;
/
Which, for the sample data:
CREATE TABLE client_master (client_id, client_name, client_budget) AS
SELECT 1, 'Alice', 100 FROM DUAL UNION ALL
SELECT 2, 'Beryl', 200 FROM DUAL UNION ALL
SELECT 3, 'Carol', 300 FROM DUAL UNION ALL
SELECT 4, 'Debra', 400 FROM DUAL UNION ALL
SELECT 5, 'Emily', 500 FROM DUAL;
CREATE TABLE audit_table (client_id, client_name, client_budget, trg_type) AS
SELECT cm.*, 'X' FROM client_master cm WHERE 1 = 0;
Then after:
UPDATE client_master
SET client_budget = client_budget + 600
WHERE client_id IN (1, 2);
DELETE FROM client_master WHERE client_id IN (1, 3);
Then the audit table contains (with the row trigger also firing for the same changes):
SELECT * FROM audit_table;
CLIENT_ID
CLIENT_NAME
CLIENT_BUDGET
TRG_TYPE
1
Alice
100
R
2
Beryl
200
R
1
Alice
100
C
2
Beryl
200
C
1
Alice
700
R
3
Carol
300
R
1
Alice
700
C
3
Carol
300
C
db<>fiddle here
Same approach using compound trigger, but although is not literally a level statement trigger, because normally they refer to table level triggers.
create or replace trigger row_compound_trigger
for delete or update on client_master
compound trigger
--
-- an array structure to buffer all the row changes
--
type t_row_list is
table of client_master%rowtype index by pls_integer;
l_audit_rows t_row_list;
l_operation varchar2(1) :=
case when updating then 'U'
when deleting then 'D'
end;
before statement is
begin
--
-- initialize the array
--
l_audit_rows.delete;
end before statement;
after each row is
begin
--
-- at row level, capture all the changes into the array
-- this variables use sys_context in case you want to use it ( not needed )
--
l_audit_rows(l_audit_rows.count+1).aud_who := sys_context('USERENV','SESSION_USER');
l_audit_rows(l_audit_rows.count).aud_when := sysdate;
l_audit_rows(l_audit_rows.count).aud_operation := l_operation;
l_audit_rows(l_audit_rows.count).aud_module := sys_context('USERENV','MODULE');
if updating then
l_audit_rows(l_audit_rows.count).client_id := :new.client_id
l_audit_rows(l_audit_rows.count).client_name := :new.client_name
... all the fields
else
l_audit_rows(l_audit_rows.count).client_id := :old.client_id
l_audit_rows(l_audit_rows.count).client_name := :old.client_name
... all the fields
end if;
end after each row;
after statement is
begin
--
-- then at completion, do a single insert of all the rows into our audit table
--
forall i in 1 .. l_audit_rows.count
insert into audit_table values l_audit_rows(i);
l_audit_rows.delete;
end after statement;
end;
/

How to create a trigger to allow both manual and auto primary key in sql developer with oracle 11g

I am attempting to create a trigger (using SQL Developer with Oracle 11g) that will allow manual insertions onto the primary key, and if a record is created without a specified primary key it will assign one from a sequence. First I tried to use a select statement in the trigger that checks if the id generated by the sequence is already in the table because of manual insertion :
DROP TABLE testing;
DROP SEQUENCE testing_seq;
CREATE TABLE testing (
id_number NUMBER PRIMARY KEY,
test_data VARCHAR(50)
);
CREATE SEQUENCE testing_seq
MINVALUE 1
MAXVALUE 10000
START WITH 1
INCREMENT BY 1
NOORDER
NOCYCLE;
CREATE OR REPLACE TRIGGER auto_testing_id BEFORE
INSERT ON testing
FOR EACH ROW
DECLARE
tmp NUMBER;
seq NUMBER;
BEGIN
IF :NEW.id_number IS NULL THEN
seq := testing_seq.nextval;
SELECT
(SELECT 1 FROM testing WHERE id_number = seq
) INTO tmp FROM dual;
while (tmp = 1)
loop
seq := testing_seq.nextval;
SELECT
(SELECT 1 FROM testing WHERE id_number = seq
) INTO tmp FROM dual;
END loop;
:NEW.id_number := seq;
END IF;
END;
/
INSERT INTO testing VALUES(1,'test1');
INSERT INTO testing (test_data) VALUES('test2');
SELECT * FROM testing;
Table TESTING dropped.
Sequence TESTING_SEQ dropped.
Table TESTING created.
Sequence TESTING_SEQ created.
Trigger AUTO_TESTING_ID compiled
1 row inserted.
1 row inserted.
ID_NUMBER TEST_DATA
---------- --------------------------------------------------
1 test1
2 test2
This works for manually created insertions, but not if I try to insert using a select statement. I believe this is because I am referencing the table being inserted on inside the trigger.
I tried a trigger without the check, but as expected if the trigger created an id that was already in the table it threw a unique constraint error
CREATE OR REPLACE TRIGGER auto_testing_id2 BEFORE
INSERT ON testing
FOR EACH ROW
DECLARE
BEGIN
IF :NEW.id_number is null
then
:NEW.id_number := testing_seq.nextval;
end if;
end;
/
Trigger AUTO_TESTING_ID2 compiled
1 row inserted.
Error starting at line : 59 in command -
INSERT INTO testing (test_data) VALUES('test2')
Error report -
SQL Error: ORA-00001: unique constraint (KATRINA_LEARNING.SYS_C001190313) violated
00001. 00000 - "unique constraint (%s.%s) violated"
*Cause: An UPDATE or INSERT statement attempted to insert a duplicate key.
For Trusted Oracle configured in DBMS MAC mode, you may see
this message if a duplicate entry exists at a different level.
*Action: Either remove the unique restriction or do not insert the key.
ID_NUMBER TEST_DATA
---------- --------------------------------------------------
1 test1
I tried to catch this error (using error name DUP_VAL_ON_INDEX), and then loop it until it found the next number in the sequence that isn't in the table (with and without error catching), but it wouldn't even send up a test error message, and when I added the loop it wouldn't compile...
Can anyone please help me create a trigger that works without using a select statement to see if the sequence nextval is already used?
The example I'm adding here is clunky and performance here would be poor, but I wanted to put out an idea that could perhaps be a starting place.
You mentioned that you don't want to SELECT to check whether NEXTVAL is already used. If you meant that you didn't want to have to perform any SELECT at all, then this answer would be cheating as it does include a SELECT statement. But if you only want to avoid the mutating-table problem, then it could be a possibility.
The approach I'll add here takes a few steps to avoid collision, and runs them anytime a non-NULL value is provided as a key. It is currently set up to fire per-row, but if entire statements will be all-NULL or all-non-NULL then it could possible be changed to a statement- or compound-trigger to improve efficiency a little. In any event, the collision-avoidance is inefficient in this example.
General steps:
- If NULL, just use the NEXTVAL
- If non-NULL, check the LAST_VALUE and CACHE for the SEQUENCE against the provided value.
- If the provided value is within the CACHE (or beyond the cache) and could cause a collision, jump the SEQUENCE beyond the provided value and throw away the values in the cache.
Create the test table/sequence:
CREATE TABLE MY_TABLE (
MY_TABLE_ID NUMBER NOT NULL PRIMARY KEY
);
--Default cache here 20
CREATE SEQUENCE MY_SEQUENCE;
Create an autonomous synchronizer. Please note, this is not at all efficient, and concurrency could be a real problem (A serializing alternative is below).
It assumes a CACHE of at least 1 and may be incompatible with NOCACHE. (Actually the whole situation might be simpler with a NOCACHE SEQUENCE though)
CREATE OR REPLACE PROCEDURE SYNC_MY_SEQUENCE(P_CANDIDATE_MAX_VALUE IN NUMBER)
IS
PRAGMA AUTONOMOUS_TRANSACTION;
V_LAST_NUMBER NUMBER;
V_CACHE_SIZE NUMBER;
V_SEQUENCE_GAP NUMBER;
V_SEQUENCE_DELTA NUMBER;
V_NEXTVAL NUMBER;
BEGIN
SELECT
LAST_NUMBER,
CACHE_SIZE
INTO V_LAST_NUMBER, V_CACHE_SIZE
FROM USER_SEQUENCES
WHERE SEQUENCE_NAME = 'MY_SEQUENCE';
--Only do anything if the provided value could cause a collision.
IF P_CANDIDATE_MAX_VALUE >= (V_LAST_NUMBER - V_CACHE_SIZE)
THEN
-- Get the delta, in case the provided value is way way higher than the SEQUENCE
V_SEQUENCE_DELTA := P_CANDIDATE_MAX_VALUE + V_CACHE_SIZE - V_LAST_NUMBER ;
-- Use the biggest gap to get a safe zone when resetting the SEQUENCE
V_SEQUENCE_GAP := GREATEST(V_SEQUENCE_DELTA, V_CACHE_SIZE);
-- Set the increment so the distance between the last_value and the safe zone can be moved in one jump
EXECUTE IMMEDIATE 'ALTER SEQUENCE MY_SEQUENCE INCREMENT BY '||V_SEQUENCE_GAP;
-- Jump to the safe zone.
V_NEXTVAL := MY_SEQUENCE.NEXTVAL;
-- Reset increment. Note there is a space here that other sessions could get big NEXTVALs from concurrent access
EXECUTE IMMEDIATE 'ALTER SEQUENCE MY_SEQUENCE INCREMENT BY 1';
--Chew through the rest of at least one cache cycle.
FOR CACHE_POINTER IN 1..V_CACHE_SIZE LOOP
V_NEXTVAL := MY_SEQUENCE.NEXTVAL;
END LOOP;
END IF;
COMMIT;
END;
/
EDIT: it would be even more costly, but one might be able to serialize access to manage concurrency with something like the below alternative:
CREATE OR REPLACE PROCEDURE SYNC_MY_SEQUENCE(P_CANDIDATE_MAX_VALUE IN NUMBER)
IS
PRAGMA AUTONOMOUS_TRANSACTION;
V_LAST_NUMBER NUMBER;
V_CACHE_SIZE NUMBER;
V_SEQUENCE_GAP NUMBER;
V_SEQUENCE_DELTA NUMBER;
V_NEXTVAL NUMBER;
V_LOCK_STATUS NUMBER;
V_LOCK_HANDLE VARCHAR2(64);
C_LOCK_KEY CONSTANT VARCHAR2(20) := 'SYNC_MY_SEQUENCE';
BEGIN
DBMS_LOCK.ALLOCATE_UNIQUE (C_LOCK_KEY,V_LOCK_HANDLE,10);
--Serialize access
V_LOCK_STATUS := DBMS_LOCK.REQUEST(
LOCKHANDLE => V_LOCK_HANDLE,
LOCKMODE => DBMS_LOCK.X_MODE,
TIMEOUT => 10,
RELEASE_ON_COMMIT => TRUE);
SELECT
LAST_NUMBER,
CACHE_SIZE
INTO V_LAST_NUMBER, V_CACHE_SIZE
FROM USER_SEQUENCES
WHERE SEQUENCE_NAME = 'MY_SEQUENCE';
IF P_CANDIDATE_MAX_VALUE >= (V_LAST_NUMBER - V_CACHE_SIZE)
THEN
V_SEQUENCE_DELTA := P_CANDIDATE_MAX_VALUE + V_CACHE_SIZE - V_LAST_NUMBER ;
V_SEQUENCE_GAP := GREATEST(V_SEQUENCE_DELTA, V_CACHE_SIZE);
EXECUTE IMMEDIATE 'ALTER SEQUENCE MY_SEQUENCE INCREMENT BY '||V_SEQUENCE_GAP;
V_NEXTVAL := MY_SEQUENCE.NEXTVAL;
EXECUTE IMMEDIATE 'ALTER SEQUENCE MY_SEQUENCE INCREMENT BY 1';
FOR CACHE_POINTER IN 1..V_CACHE_SIZE LOOP
V_NEXTVAL := MY_SEQUENCE.NEXTVAL;
END LOOP;
END IF;
COMMIT;
END;
/
Create the trigger:
CREATE OR REPLACE TRIGGER MAYBE_SET
BEFORE INSERT ON MY_TABLE
FOR EACH ROW
BEGIN
IF :NEW.MY_TABLE_ID IS NULL
THEN
:NEW.MY_TABLE_ID := MY_SEQUENCE.NEXTVAL;
ELSE
SYNC_MY_SEQUENCE(:NEW.MY_TABLE_ID);
END IF;
END;
/
Then test it:
INSERT INTO MY_TABLE SELECT LEVEL FROM DUAL CONNECT BY LEVEL < 5;
SELECT * FROM MY_TABLE ORDER BY 1 ASC;
MY_TABLE_ID
1
2
3
4
It just used the NEXTVAL each time.
Then add a collidable value. Adding this will fire the sproc and do the extra work to push the SEQUENCE into a safe zone.
INSERT INTO MY_TABLE VALUES(5);
SELECT * FROM MY_TABLE ORDER BY 1 ASC;
MY_TABLE_ID
1
2
3
4
5
Then use NULL again:
INSERT INTO MY_TABLE VALUES(NULL);
SELECT * FROM MY_TABLE ORDER BY 1 ASC;
MY_TABLE_ID
1
2
3
4
5
41
The SEQUENCE had a costly operation to get there, but has settled and didn't collide.
If other provided values are below the SEQUENCE visibility, they add freely and don't change the NEXTVAL:
INSERT INTO MY_TABLE VALUES(7);
INSERT INTO MY_TABLE VALUES(19);
INSERT INTO MY_TABLE VALUES(-9999);
INSERT INTO MY_TABLE VALUES(NULL);
SELECT * FROM MY_TABLE ORDER BY 1 ASC;
MY_TABLE_ID
-9999
1
2
3
4
5
7
19
41
42
If the gap is huge, it jumps way out there:
INSERT INTO MY_TABLE VALUES(50000);
INSERT INTO MY_TABLE VALUES(NULL);
SELECT * FROM MY_TABLE ORDER BY 1 ASC;
MY_TABLE_ID
-9999
1
2
3
4
5
7
19
41
42
50000
50022
This could be too costly for your use case, and I haven't tested in in a RAC, but wanted to throw out an idea that can avoid collisions.

In oracle procedure how to fetch the PKId just added to be added as FK ID for next table

In an oracle procedure, I need to insert data in EmployeeHeader table and then later insert the PK id of this table as the FK id of EmployeeDetails table. How can we achieve this?
INSERT INTO EmployeeHeader(
HEADER_PK_ID
empNo
)
VALUES(
HEADER_PK_ID_SEQ.NEXTVAL,
'SOMETHING'
);
INSERT INTO EmployeeDetails (
DTLHEADER_PK_ID,
HEADER_fK_ID
empname,
age
)
VALUES(
DTLHEADER_PK_ID_SEQ.NEXTVAL,
HEADER_PK_IDn, -- (THIS NEEDS TO BE FETCHED FROM EmployeeHeader)
21
);
You can use currval in most cases:
select HEADER_PK_ID_SEQ.CURRVAL
from dual;
You might need to wrap the two inserts in a single transaction, if you want the values to be safe for concurrent inserts.
Use the RETURNING clause of the INSERT statement:
DECLARE
nHeader_pk_id NUMBER;
BEGIN
INSERT INTO EmployeeHeader
(HEADER_PK_ID, EMPNO)
VALUES
(HEADER_PK_ID_SEQ.NEXTVAL, 'SOMETHING')
RETURNING HEADER_PK_ID INTO nHeader_pk_id;
INSERT INTO EmployeeDetails
(DTLHEADER_PK_ID, HEADER_FK_ID, EMPNAME, AGE)
VALUES
(DTLHEADER_PK_ID_SEQ.NEXTVAL, nHeader_pk_id, 'Somebody', 21);
END;
My personal preference is to use ON INSERT triggers to handle the population of primary key fields, in the following manner:
CREATE OR REPLACE TRIGGER EMPLOYEEHEADER_BI
BEFORE INSERT ON EMPLOYEEHEADER
FOR EACH ROW
BEGIN
IF :NEW.HEADER_PK_ID IS NULL THEN
:NEW.HEADER_PK_ID := HEADER_PK_ID_SEQ.NEXTVAL;
END IF;
END EMPLOYEEHEADER_BI;
CREATE OR REPLACE TRIGGER EMPLOYEEDETAILS_BI
BEFORE INSERT ON EMPLOYEEDETAILS
FOR EACH ROW
BEGIN
IF :NEW.DTLHEADER_PK_ID IS NULL THEN
:NEW.DTLHEADER_PK_ID := DTLHEADER_PK_ID_SEQ.NEXTVAL;
END IF;
END EMPLOYEEDETAILS_BI;
and the INSERT statements become:
DECLARE
nHeader_pk_id NUMBER;
nDtlheader_pk_id NUMBER;
BEGIN
INSERT INTO EmployeeHeader
(EMPNO) -- Note: PK field not mentioned - will be populated by trigger
VALUES
('SOMETHING')
RETURNING HEADER_PK_ID INTO nHeader_pk_id;
INSERT INTO EmployeeDetails
(HEADER_FK_ID, EMPNAME, AGE) -- Note: PK field not mentioned - will be populated by trigger
VALUES
(nHeader_pk_id, 'Somebody', 21)
RETURNING DTLHEADER_PK_ID INTO nDtlheader_pk_id;
END;
(I use the IF pk_field IS NULL THEN construct because I often need to copy data from production to development databases and wish to preserve any key values pulled from production to simplify debugging. If you don't have this requirement you can eliminated the IS NULL check and just assign the sequence's NEXTVAL directly to the column).
Done in this manner the application code doesn't need to know or care about which sequence is used to generate the primary key value for a particular table, and the primary key field is always going to end up populated.
Best of luck.
You can use the RETURNING clause.
Here's an example:
create sequence test_seq1
start with 1
maxvalue 999
minvalue 1
nocycle
cache 20
noorder;
create sequence test_seq2
start with 100
maxvalue 999
minvalue 1
nocycle
cache 20
noorder;
create table test_tab_p (col1 number, col2 number);
create table test_tab_c (col1 number, col2 number, col3 number);
declare
v_p_col2 number := 1;
v_c_col3 number := 10;
v_p_col1 number;
begin
insert into test_tab_p (col1, col2)
values (test_seq1.nextval, v_p_col2)
returning col1 into v_p_col1;
insert into test_tab_c (col1, col2, col3)
values (test_seq2.nextval, v_p_col1, v_c_col3);
commit;
end;
/
select * from test_tab_p;
COL1 COL2
---------- ----------
1 1
select * from test_tab_c;
COL1 COL2 COL3
---------- ---------- ----------
100 1 10
Use currval
INSERT INTO EmployeeHeader
(header_pk_id, empNo)
VALUES
(header_pk_id_seq.nextval, 'SOMETHING');
INSERT INTO EmployeeDetails
(dtlheader_pk_id, header_fk_id, empname, age)
VALUES
(dtlheader_pk_id_seq.nextval, header_pk_id_seq.currval, 21);
currval is safe to use here. It always returns the last value obtained by nextval for the current connection. So even if other transactions (which means a different connection) calls nextval between those two statements, currval will still reflect the value of "this" nextval call.
Quote from the manual:
Each user that references a sequence has access to his or her current sequence number, which is the last sequence generated in the session. A user can issue a statement to generate a new sequence number or use the current number last generated by the session. After a statement in a session generates a sequence number, it is available only to this session.
The very efficient way will be just save the seq id value into a variable and then use it for the whole transaction. Below is the mentioned example. Let me know for any issues.
DECLARE
lv_pkid PLS_INTEGER:=SEQ.NEXTVAL;
BEGIN
INSERT
INTO EmployeeHeader
(
HEADER_PK_ID,
empNo
)
VALUES
(
lv_pkid,
'SOMETHING'
);
INSERT
INTO EmployeeDetails
(
DTLHEADER_PK_ID,
HEADER_fK_ID,
empname,
age
)
VALUES
(
DTLHEADER_PK_ID_SEQ.NEXTVAL,
lv_pkid, -- (THIS NEEDS TO BE FETCHED FROM EmployeeHeader)
21
);
COMMIT;
END;

How to handle Oracle Error [ Unique Constraint ] error

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;

IF Statement inside Trigger Clause

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