How to skip unique constraint error - sql

I am trying to insert values into sql table, but I am getting this error in sql query
SQL Error: ORA-00001: unique constraint (uniqueKey) 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.
Is there a way to skip this error and continue insert. Something like this
try
insert query
catch (unique constraint error)
continue inserting other values

There exists hint ignore_row_on_dupkey_index(<table name>, <unique index name>).
HUSQVIK#hq_pdb_tcp> CREATE TABLE tmp (val NUMBER CONSTRAINT pk_tmp PRIMARY KEY);
Table created.
HUSQVIK#hq_pdb_tcp> INSERT /*+ ignore_row_on_dupkey_index(tmp, pk_tmp) */ INTO tmp (val) SELECT 1 FROM DUAL CONNECT BY LEVEL <= 3;
1 row created.
See that I insert three values of 1 and only one row was created.

There is a LOG ERRORS clause which allows you to log the rows that cause errors in to an error table - this error table is created using a DBMS package:
DBMS_ERRLOG.CREATE_ERROR_LOG(table_being_inserted_into ,name_of_table_for_errors ,NULL,NULL,TRUE);
https://docs.oracle.com/cd/B28359_01/appdev.111/b28419/d_errlog.htm
Function Signature:
DBMS_ERRLOG.CREATE_ERROR_LOG (
dml_table_name IN VARCHAR2,
err_log_table_name IN VARCHAR2 := NULL,
err_log_table_owner IN VARCHAR2 := NULL,
err_log_table_space IN VARCHAR2 := NULL,
skip_unsupported IN BOOLEAN := FALSE);
Then in your insert statement you end it with a log errors clause:
LOG ERRORS INTO your_error_table_name ( 'description of your choosing' ) REJECT LIMIT UNLIMITED;
You can choose to accept a reject limit of a fixed number, allowing you to specify in effect a tolerance to errors before it throws a real error instead of just allowing the row to be place in an error table.

simple sample is insert in for loop and ignore exceptions:
begin
for rc in (select * from <your query> loop
begin
insert into t1(...) values (...);
exceptions when others then
null;--ignore any exceptions do nothing
end;
end loop;
end
other sample - same idea but use FORALL bulk operation and SAVE EXCEPTIONS
declare
cursor C is
select ID, OWNER, OBJECT_NAME, SUBOBJECT_NAME, OBJECT_ID, DATA_OBJECT_ID,
decode( mod(rownum,100000), 1, rpad('*',20,'*'), OBJECT_TYPE ) object_type,
CREATED, LAST_DDL_TIME, TIMESTAMP, STATUS, TEMPORARY, GENERATED, SECONDARY
from big_table;
type array is table of c%rowtype;
l_data array;
dml_errors EXCEPTION;
PRAGMA exception_init(dml_errors, -24381);
l_errors number;
l_errno number;
l_msg varchar2(4000);
l_idx number;
begin
open c;
loop
fetch c bulk collect into l_data limit 100;
begin
forall i in 1 .. l_data.count SAVE EXCEPTIONS
insert into t2 values l_data(i);
exception
when DML_ERRORS then
l_errors := sql%bulk_exceptions.count;
for i in 1 .. l_errors
loop
l_errno := sql%bulk_exceptions(i).error_code;
--do smth with the exceptions
end loop;
end;
exit when c%notfound;
end loop;
close c;
end;
more information see on AskTom and OraMagazine
https://asktom.oracle.com/pls/asktom/f?p=100:11:0%3A%3A%3A%3AP11_QUESTION_ID:1422998100346727312
http://www.oracle.com/technetwork/issue-archive/2012/12-sep/o52plsql-1709862.html

Related

How to include a SUBSELECT in VALUES of INSERT to take values from different row?

I want to make a trigger that will insert a value from a connected row. For example I have a table with 3 rows as below:
I create a trigger that will work once row 3 and 4 are deleted (in this case will be deleted at the same time). And I want to record invnr and extinvnr from row 1 based on idparent=id. I cannot seem to make it work though.
CREATE OR REPLACE TRIGGER LOG_DELETEDPAYMENTS
BEFORE DELETE ON payments
FOR EACH ROW
BEGIN
IF :old.invnr IS NULL THEN
INSERT INTO TABLE_LOG_DELETEDPAYMENTS (table_name, invnr, extinvnr, invdate, transactionid, info, createdby, deleted_by, date_of_delete)
values ('payments', :old.invnr, :old.extinvnr, :old.invdate, :old:transactionid, :old.info, :old.createdby, sys_context('userenv','OS_USER'), SYSDATE);
END IF;
END;
How can I incorporate this into the trigger above?
Try it this way:
create or replace TRIGGER LOG_DELETEDPAYMENTS
BEFORE DELETE ON payments
FOR EACH ROW
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
Declare
my_invnr PAYMENTS.INVNR%TYPE;
my_extinvnr PAYMENTS.EXTINVNR%TYPE;
Begin
IF :old.INVNR IS NULL THEN
Select INVNR, EXTINVNR
Into my_invnr, my_extinvnr
From PAYMENTS
Where ID = :old.IDPARENT;
--
INSERT INTO TABLE_LOG_DELETEDPAYMENTS (table_name, invnr, extinvnr, invdate, transactionid, info, createdby, deleted_by, date_of_delete)
values ('payments', my_invnr, my_extinvnr, :old.invdate, :old:transactionid, :old.info, :old.createdby, sys_context('userenv','OS_USER'), SYSDATE);
END IF;
End;
END;
You should select the values of INVNR and EXTINVNR based on ID - IDPARENT relationship and store it in the variables (my_invnr and my_extinvnr).
Those variables are used in INSERT into the log statement.
Because of the Select ... Into statement that is reading the affected table - trigger would fail with table PAYMENTS is mutating error.
To avoid that (to separate transaction from the table) you should Declare the PRAGMA AUTONOMOUS_TRANSACTION.
There will be two rows inserted into LOG as the trigger runs FOR EACH (deleted) ROW.
Regards...
This assumes your are on release 12c or greater of Oracle database.
CREATE OR REPLACE PACKAGE LOG_DELETEDPAYMENTS_PKG
AS
-- need a locally defined type for use in trigger
TYPE t_payments_tbl IS TABLE OF payments%ROWTYPE INDEX BY PLS_INTEGER;
END LOG_DELETEDPAYMENTS_PKG;
CREATE OR REPLACE PACKAGE BODY LOG_DELETEDPAYMENTS_PKG
AS
BEGIN
-- could also put the trigger code here and pass the type as a parameter to a procedure
NULL;
END LOG_DELETEDPAYMENTS_PKG;
CREATE OR REPLACE TRIGGER LOG_DELETEDPAYMENTS_CT
FOR DELETE ON payments
COMPOUND TRIGGER
l_tab LOG_DELETEDPAYMENTS_PKG.t_payments_tbl;
l_count PLS_INTEGER:= 0;
BEFORE EACH ROW IS
BEGIN
-- capture the deletes in local type
l_count := l_count + 1;
l_tab(l_count).invnr := :old.invnr;
l_tab(l_count).extinvnr := :old.extinvnr;
l_tab(l_count).invdate := :old.invdate;
l_tab(l_count).transactionid := :old.transactionid;
l_tab(l_count).info := :old.info;
l_tab(l_count).createdby := :old.createdby;
l_tab(l_count).idparent := :old.idparent;
l_tab(l_count).id := :old.id;
END BEFORE EACH ROW;
AFTER STATEMENT IS
BEGIN
FOR i IN l_tab.first .. l_tab.COUNT LOOP
IF(l_tab(i).invnr IS NULL) THEN
-- if the invoice number is NULL, then get info from parent
SELECT p.invnr
,p.extinvnr
INTO l_tab(i).invnr
,l_tab(i).extinvnr
FROM TABLE(l_tab) p
WHERE p.id = l_tab(i).idparent;
END IF;
END LOOP;
-- log all deletes
FORALL i IN 1 .. l_tab.COUNT
INSERT INTO LOG_DELETEDPAYMENTS
(table_name, invnr, extinvnr, invdate, transactionid, info, createdby, deleted_by, date_of_delete)
VALUES
('payments', l_tab(i).invnr, l_tab(i).extinvnr, l_tab(i).invdate, l_tab(i).transactionid, l_tab(i).info, l_tab(i).createdby, sys_context('userenv','OS_USER'), SYSDATE);
l_tab.delete;
END AFTER STATEMENT;
END LOG_DELETEDPAYMENTS_CT;

getting error ORA-01400:cannot insert NULL ... while inserting data with help of rowtype

I'm trying to insert data in a backup table with help of rowtype as below
declare
vl_bkp_rec schema.table1%ROWTYPE;
BEGIN
FOR cur_rec IN
(SELECT *
FROM schema.table1
WHERE column_1 ='3f1d6348-014e-1000-8461-700c000493e0'
AND primary_key_column NOT IN ('8dc81f6e-0156-1000-8291-700e000493e0')
)
LOOP
INSERT INTO schema.backup_table VALUES vl_bkp_rec;
END LOOP;
COMMIT;
EXCEPTION
WHEN OTHERS THEN
lv_err_msg := SUBSTR(SQLERRM, 1, 2999);
DBMS_OUTPUT.PUT_LINE('Handled - error while executing script. =>'|| lv_err_msg );
ROLLBACK;
END;
/
i'm getting below error
Handled - error while executing script. =>ORA-01400: cannot insert NULL into ("schema"."backup_table"."primary_key_column")
but table1 and backup_table have exactly same structure. (created backup_table as below)
CREATE TABLE schema.backup_table AS
(SELECT * FROM schema.table1 WHERE rownum <1
);
and select query used above fetches valid data. What am I doing wrong here?
You need to use your variable in the below way. Currently in your code the variable declared as table type is not getting filled: See below how to use it.
declare
vl_bkp_rec table1%ROWTYPE;
BEGIN
FOR cur_rec IN
(SELECT *
FROM table1
)
LOOP
vl_bkp_rec:=cur_rec; --Assign values of the cursor variable to your variable
INSERT INTO backup_table VALUES vl_bkp_rec;
END LOOP;
COMMIT;
EXCEPTION
WHEN OTHERS THEN
--lv_err_msg := SUBSTR(SQLERRM, 1, 2999); ---you need to decalre it befor using it
DBMS_OUTPUT.PUT_LINE('Handled - error while executing script. =>' );
ROLLBACK;
END;
/

Separate handling for two posisble unique constraint exceptions on the same insert

I'm trying to catch two different exceptions from the same statement in PL/SQL.
If one exception is raised then it needs to keep on looping
If the other one is raised then it needs to exit the loop
And if the insert is successful it needs to exit the loop.
Here is the code I use:
create or replace procedure NewCouponGen
IS
v_min number(10) := 1000;
v_max number(10) := 99999;
v_winkel_id varchar2(200);
v_suc number(1,0);
v_new_code number(10);
CURSOR c_winkel IS
SELECT id
FROM WINKEl;
BEGIN
OPEN c_winkel;
LOOP
FETCH c_winkel INTO v_winkel_id;
v_suc := 0;
WHILE v_suc = 0
LOOP
select floor(dbms_random.value(v_min,v_max)) num INTO v_new_code from dual;
INSERT INTO WINKEL_COUPON (WINKEL_ID, COUPON_ID) VALUES (v_winkel_id, v_new_code);
-- CATCH UNQUE EXEPTION
--IF v_winkel_id != UNIQUE THEN v_suc = 1
--IF v_new_code != UNIQUE THEN KEEP ON LOOPING
--IF INSERT IS SUCCES THEN v_suc = 1
END LOOP;
EXIT WHEN c_winkel%notfound;
END LOOP;
CLOSE c_winkel;
END NewCouponGen;
The simplest thing is not to hit the first exception at all. There is a hint to ignore the duplicate violation, but that would apply to both unique constraints, so it isn't useful here. You could query to see if there is already a record with the WINKEL_ID and only insert if there is not; or as a single statement you could use a merge:
create or replace procedure NewCouponGen
IS
v_min number(10) := 1000;
v_max number(10) := 99999;
v_winkel_id varchar2(200);
v_new_code number(10);
CURSOR c_winkel IS
SELECT id
FROM WINKEl;
BEGIN
OPEN c_winkel;
LOOP
FETCH c_winkel INTO v_winkel_id;
EXIT WHEN c_winkel%notfound;
LOOP
BEGIN
v_new_code := floor(dbms_random.value(v_min,v_max));
MERGE INTO WINKEL_COUPON TGT
USING (SELECT v_winkel_id AS WINKEL_ID, v_new_code AS COUPON_ID FROM DUAL) SRC
ON (TGT.WINKEL_ID = SRC.WINKEL_ID)
WHEN NOT MATCHED THEN
INSERT (TGT.WINKEL_ID, TGT.COUPON_ID) VALUES (SRC.WINKEL_ID, SRC.COUPON_ID);
EXCEPTION
WHEN dup_val_on_index THEN
CONTINUE; -- duplicate coupon ID
END;
EXIT; -- merge was skipped because winkel ID exists, or was successful
END LOOP;
END LOOP;
CLOSE c_winkel;
END NewCouponGen;
/
The merge will only try to insert if it didn't see that a record already existed for the WINKEL_ID, so you won't get a unique constraint violation from that column. If you do get one from the COUPON_ID constraint then the exception handler on that inner block enclosing the merge - which exists only allow the exception to be caught - will send you around the loop again.
I've also taken out the v_suc flag completely; and moved the exit when clause to straight after the fetch - otherwise you will always try to insert two values for the last ID from the cursor; and taken out the context switch from the select .. from dual since you can just assign that random value directly to the variable.
You don't really need that v_new_code variable either, you can get the value in the merge instead:
MERGE INTO WINKEL_COUPON TGT
USING (SELECT v_winkel_id AS WINKEL_ID,
floor(dbms_random.value(v_min,v_max)) AS COUPON_ID FROM DUAL) SRC
ON (TGT.WINKEL_ID = SRC.WINKEL_ID)
WHEN NOT MATCHED THEN
INSERT (TGT.WINKEL_ID, TGT.COUPON_ID) VALUES (SRC.WINKEL_ID, SRC.COUPON_ID);
You can use CONTINUE to keep on looping and EXIT WHEN to exit the loop
LOOP -- After CONTINUE statement, control resumes here
select floor(dbms_random.value(v_min,v_max)) num INTO v_new_code from dual;
IF (v_new_code =='keep on looping condition') THEN
CONTINUE; --stops current loop and goes on to next iteration
END IF;
INSERT INTO WINKEL_COUPON (WINKEL_ID, COUPON_ID) VALUES (v_winkel_id, v_new_code);
EXIT WHEN v_suc==1; --or other exit conditions
END LOOP;

ORA-06502: PL/SQL: numeric or value error: number precision too large

I'm trying to run the following insert command in Oracle SQL Developer:
insert into work_comp_rates (company_id, work_comp_rt)
values ('101', 0.11);
Which gives me this error: "ORA-06502: PL/SQL: numeric or value error: number precision too large"
There is a trigger attached:
create or replace
TRIGGER APPS.work_codes_trig
before insert or update ON APPS.WORK_COMP_RATES for each row
begin
if inserting then
if :NEW.RID is null then
:NEW.RID := it_api.gen_pk;
end if;
:NEW.CREATED_ON := sysdate;
end if;
if updating then
:NEW.MODIFIED_ON := sysdate;
end if;
end;
If I replace
:NEW.RID := it_api.gen_pk;
with
:NEW.RID := 599;
the insert statement works.
IT_API Body:
create or replace
package body it_api
as
-- generates and returns unique number used for primary key values
function gen_pk
return number
is
l_pk number := 0;
begin
for c1 in (
select to_number(sys_guid(),'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX') pk
from dual )
loop
l_pk := c1.pk;
exit;
end loop;
return l_pk;
end gen_pk;
end it_api;
I don't know Oracle very well and that script was written by somebody else. So any help is appreciated!
I created a sequence rate_seq that increments values by 1 and replaced
:NEW.RID := it_api.gen_pk;
with
select rate_seq.nextval
into :new.rid
from dual;
That fixed the problem.

Move large data between tables in oracle with bulk insert

I want to move 1 million rows of data to another table. Im using query:
insert into Table1
select * from Table2;
in my PL/SQL function. But this way is too slow.
How can I do this with Bulk Insert method?
Source and destination table has same structure.
Tables have hash partition and 1 index.
Forget about bulk insert. Because the insert into select is the best bulk you can load.
The fastest would be to disable the indexes (mark them unusable) and do this in a SINGLE
insert:
insert /*+ append */ into TARGET
select COLS
from SOURCE;
commit;
and rebuild the indexes using UNRECOVERABLE (and maybe even parallel).
PS: If the table is partitioned (Both source and target, you can even use parallel inserts)
FOLLOW UP:
Check the performance of the below select
SELECT /*+ PARALLEL(A 4)
USE_HASH(A) ORDERED */
YOUR_COLS
FROM
YOUR_TABLE A
WHERE
ALL_CONDITIONS;
If faster then
INSERT /*+ APPEND */
INTO
TARGET
SELECT /*+ PARALLEL(A 4)
USE_HASH(A) ORDERED */
YOUR_COLS
FROM
YOUR_TABLE A
WHERE
ALL_CONDITIONS;
USING Bulk Collect
Converting to collections and bulk processing can increase the volume and complexity of your code. If you need a serious boost in performance, however, that increase is well-justified.
Collections, an evolution of PL/SQL tables that allows us to manipulate many variables at once, as a unit. Collections, coupled with two new features introduced with Oracle 8i, BULK_COLLECT and FORALL, can dramatically increase the performance of data manipulation code within PL/SQL.
CREATE OR REPLACE PROCEDURE test_proc (p_array_size IN PLS_INTEGER DEFAULT 100)
IS
TYPE ARRAY IS TABLE OF all_objects%ROWTYPE;
l_data ARRAY;
CURSOR c IS SELECT * FROM all_objects;
BEGIN
OPEN c;
LOOP
FETCH c BULK COLLECT INTO l_data LIMIT p_array_size;
FORALL i IN 1..l_data.COUNT
INSERT INTO t1 VALUES l_data(i);
EXIT WHEN c%NOTFOUND;
END LOOP;
CLOSE c;
END test_proc;
this procedure created by rohit sahani......
using bulk collect i create this procedure
in this procedure i also did update and insert in table and
in this procedure i did collect data in bulk and then update in salary with the use of forall statement .
CREATE OR REPLACE PROCEDURE sam_proc_1(l_salary NUMBER)
IS
l_address VARCHAR(100) := 'karnatka';
a_address VARCHAR(100) := 'bihar';
c_limit PLS_INTEGER := 100;
TYPE employee_ids_t IS TABLE OF emp.emp_id%TYPE;
l_employee_ids employee_ids_t;
CURSOR employees_cur IS SELECT emp_id FROM emp;
-- CURSOR CUR1 IS SELECT EMP_ID, FNAME, ADDRESS FROM EMP;
BEGIN
OPEN employees_cur;
LOOP
FETCH employees_cur BULK COLLECT INTO l_employee_ids LIMIT c_limit;
EXIT WHEN l_employee_ids.COUNT = 0;
-----here start updating
FORALL indx IN 1 .. l_employee_ids.COUNT SAVE EXCEPTIONS
UPDATE emp a
SET a.salary = a.salary + l_salary
WHERE a.emp_id = l_employee_ids(indx);
commit;
END LOOP;
-----------
BEGIN
UPDATE emp v
SET v.address = l_address
WHERE v.address = a_address;
COMMIT;
EXCEPTION WHEN
OTHERS THEN
RAISE_APPLICATION_ERROR(-20001,'An error encountered while updating in address - '
||SQLCODE||' -ERROR- '||SQLERRM);
END;
---------
BEGIN
FOR I IN ( SELECT EMP_ID, FNAME, ADDRESS FROM EMP)
LOOP
INSERT INTO students
( stu_id, stu_name, stu_address)
VALUES
(i.emp_id, i.fname, i.address);
END LOOP;
COMMIT;
EXCEPTION WHEN
OTHERS THEN
RAISE_APPLICATION_ERROR(-20001,'An error encountered while inserting - '
||SQLCODE||' -ERROR- '||SQLERRM);
END;
------------
EXCEPTION
WHEN OTHERS
THEN
IF SQLCODE = -24381
THEN
FOR indx IN 1 .. SQL%BULK_EXCEPTIONS.COUNT
LOOP
-- Caputring errors occured during update
DBMS_OUTPUT.put_line( SQL%BULK_EXCEPTIONS (indx).ERROR_INDEX
||','||
SQL%BULK_EXCEPTIONS (indx).ERROR_CODE);
--<You can inset the error records to a table here>
END LOOP;
ELSE
RAISE;
END IF;
END sam_proc_1;
-----END PROCEDURE;c