Oracle 'INSERT ALL' ignore duplicates - oracle

I have a database table with a unique constraint on it (unique (DADSNBR, DAROLEID) pair). I am going to be inserting multiple values into this table simultaneously, so I'd like to get it done using one query - I'm assuming this would be the faster way. My query is thus:
INSERT ALL
INTO ACCESS (DADSNBR, DAROLEID) VALUES (68, 1)
INTO ACCESS (DADSNBR, DAROLEID) VALUES (68, 2)
INTO ACCESS (DADSNBR, DAROLEID) VALUES (68, 3)
INTO ACCESS (DADSNBR, DAROLEID) VALUES (68, 4)
SELECT 1 FROM DUAL
Since there are some entries within the statement that are duplicates of those already in the database, the whole insert fails and none of the rows are inserted.
Is there a way to ignore the cases where the unique constraint fails, and just insert the ones that are unique, without having to split it up into individual INSERT statements?
Edit: I realised I probably don't want to do this anyway, but I'm still curious as to whether it's possible or not.

In Oracle, statements either succeed completely or fail completely (they are atomic). However, you can add clauses in certain cases to log exceptions instead of raising errors:
using BULK COLLECT - SAVE EXCEPTIONS, as demonstrated in this thread on askTom,
or using DBMS_ERRLOG (available since 10g I think).
The second method is all automatic, here's a demo (using 11gR2):
SQL> CREATE TABLE test (pk1 NUMBER,
2 pk2 NUMBER,
3 CONSTRAINT pk_test PRIMARY KEY (pk1, pk2));
Table created.
SQL> /* Statement fails because of duplicate */
SQL> INSERT into test (SELECT 1, 1 FROM dual CONNECT BY LEVEL <= 2);
ERROR at line 1:
ORA-00001: unique constraint (VNZ.PK_TEST) violated
SQL> BEGIN dbms_errlog.create_error_log('TEST'); END;
2 /
PL/SQL procedure successfully completed.
SQL> /* Statement succeeds and the error will be logged */
SQL> INSERT into test (SELECT 1, 1 FROM dual CONNECT BY LEVEL <= 2)
2 LOG ERRORS REJECT LIMIT UNLIMITED;
1 row(s) inserted.
SQL> select ORA_ERR_MESG$, pk1, pk2 from err$_test;
ORA_ERR_MESG$ PK1 PK2
--------------------------------------------------- --- ---
ORA-00001: unique constraint (VNZ.PK_TEST) violated 1 1
You can use the LOG ERROR clause with INSERT ALL (thanks #Alex Poole), but you have to add the clause after each table:
SQL> INSERT ALL
2 INTO test VALUES (1, 1) LOG ERRORS REJECT LIMIT UNLIMITED
3 INTO test VALUES (1, 1) LOG ERRORS REJECT LIMIT UNLIMITED
4 (SELECT * FROM dual);
0 row(s) inserted.

Use the MERGE statement to handle this situation:
merge into "ACCESS" a
using
(
select 68 as DADSNBR,1 as DAROLEID from dual union all
select 68,2 from dual union all
select 68,3 from dual union all
select 68,4 from dual
) t
on (t.DADSNBR = a.DADSNBR and t.DAROLEID = a.DAROLEID)
when not matched then
insert (DADSNBR, DAROLEID)
values (t.DADSNBR, t.DAROLEID);

Related

Error unique constraint and during execution of trigger oracle

I have form in which user add information of new born baby with his/her head family name. When add information into table then getting following errors
ORA-00001: unique constraint (PK) violated
ORA-06512: at trigger_name, line 21
ORA-04088: error during execution of trigger
Trigger:
CREATE OR REPLACE TRIGGER "DB_NAME"."TRG_NBB"
AFTER INSERT ON baby
FOR EACH ROW
WHEN (new.status = 'A') DECLARE
v_1 tab_1.col_1%type;
v_2 tab_2.col_2%type;
v_3 tab_2.col_3%type;
v_4 tab_2.col_4%type;
v_5 tab_2.col_5%type;
v_6 date;
newmofid number;
BEGIN
select max(nvl(col_2,0))+1 into newmofid from tab_2;
SELECT distinct col_1,col_2,to_char(col,'DD-MM-YYYY') INTO v_1,v_2,v_6
from table
where tcid = :new.tcid;
SELECT col_4,col_5,col_3 into v_4,v_5,v_3
from tab_2
where col_1 = v_1
and col_2 = v_2;
INSERT INTO tab_2 (all_columns)
VALUES(variable_names);
DBMS_OUTPUT.PUT_LINE('New Born Baby successfully added to member table');
END trg_nbb;
/
ALTER TRIGGER "DB_NAME"."TRG_NBB" ENABLE;
When I execute this sql query It's take 4 to 5 seconds and increment in values very quickly
select max(nvl(col_2,0))+1 into newmofid from tab_2;
Result:
6030819791
Again execute takes 3 to 4 seconds
Result:
6030819798
How to solve this problem?
Thanks
I suspect it is MAX + 1 that causes problems:
select max(nvl(col_2,0))+1 into newmofid from tab_2;
Such a principle is in most cases wrong and will fails, especially in a multi-user environment where two (or more) users at the same time fetch the same MAX + 1 value, do some processing, and - at the time of insert - one of them succeeds (because it is the first), but the rest of them fail because such a value already exists in the table.
I suggest you switch to a sequence, e.g.
create sequence seq_baby;
and then, in your form, do
select seq_baby.nextval into newmofid from dual;

insert multiple row into table using select however table has primery key in oracle SQL [duplicate]

This question already has answers here:
How to create id with AUTO_INCREMENT on Oracle?
(18 answers)
Closed 8 years ago.
I am facing issue while inserting multiple row in one go into table because column id has primary key and its created based on sequence.
for ex:
create table test (
iD number primary key,
name varchar2(10)
);
insert into test values (123, 'xxx');
insert into test values (124, 'yyy');
insert into test values (125, 'xxx');
insert into test values (126, 'xxx');
The following statement creates a constraint violoation error:
insert into test
(
select (SELECT MAX (id) + 1 FROM test) as id,
name from test
where name='xxx'
);
This query should insert 3 rows in table test (having name=xxx).
You're saying that your query inserts rows with primary key ID based on a sequence. Yet, in your insert/select there is select (SELECT MAX (id) + 1 FROM test) as id, which clearly is not based on sequence. It may be the case that you are not using the term "sequence" in the usual, Oracle way.
Anyway, there are two options for you ...
Create a sequence, e.g. seq_test_id with the starting value of select max(id) from test and use it (i.e. seq_test_id.nextval) in your query instead of the select max(id)+1 from test.
Fix the actual subselect to nvl((select max(id) from test),0)+rownum instead of (select max(id)+1 from test).
Please note, however, that the option 2 (as well as your original solution) will cause you huge troubles whenever your code runs in multiple concurrent database sessions. So, option 1 is strongly recommended.
Use
insert into test (
select (SELECT MAX (id) FROM test) + rownum as id,
name from test
where name='xxx'
);
as a workaround.
Of course, you should be using sequences for integer-primary keys.
If you want to insert an ID/Primary Key value generated by a sequence you should use the sequence instead of selecting the max(ID)+1.
Usually this is done using a trigger on your table wich is executed for each row. See sample below:
CREATE TABLE "MY_TABLE"
(
"MY_ID" NUMBER(10,0) CONSTRAINT PK_MY_TABLE PRIMARY KEY ,
"MY_COLUMN" VARCHAR2(100)
);
/
CREATE SEQUENCE "S_MY_TABLE"
MINVALUE 1 MAXVALUE 999999999999999999999999999
INCREMENT BY 1 START WITH 10 NOCACHE ORDER NOCYCLE NOPARTITION ;
/
CREATE OR REPLACE TRIGGER "T_MY_TABLE"
BEFORE INSERT
ON
MY_TABLE
REFERENCING OLD AS OLDEST NEW AS NEWEST
FOR EACH ROW
WHEN (NEWEST.MY_ID IS NULL)
DECLARE
IDNOW NUMBER;
BEGIN
SELECT S_MY_TABLE.NEXTVAL INTO IDNOW FROM DUAL;
:NEWEST.MY_ID := IDNOW;
END;
/
ALTER TRIGGER "T_MY_TABLE" ENABLE;
/
insert into MY_TABLE (MY_COLUMN) values ('DATA1');
insert into MY_TABLE (MY_COLUMN) values ('DATA2');
insert into MY_TABLE (MY_ID, MY_COLUMN) values (S_MY_TABLE.NEXTVAL, 'DATA3');
insert into MY_TABLE (MY_ID, MY_COLUMN) values (S_MY_TABLE.NEXTVAL, 'DATA4');
insert into MY_TABLE (MY_COLUMN) values ('DATA5');
/
select * from MY_TABLE;

Oracle identity column and insert into select

Oracle 12 introduced nice feature (which should have been there long ago btw!) - identity columns. So here's a script:
CREATE TABLE test (
a INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
b VARCHAR2(10)
);
-- Ok
INSERT INTO test (b) VALUES ('x');
-- Ok
INSERT INTO test (b)
SELECT 'y' FROM dual;
-- Fails
INSERT INTO test (b)
SELECT 'z' FROM dual UNION ALL SELECT 'zz' FROM DUAL;
First two inserts run without issues providing values for 'a' of 1 and 2. But the third one fails with ORA-01400: cannot insert NULL into ("DEV"."TEST"."A"). Why did this happen? A bug? Nothing like this is mentioned in the documentation part about identity column restrictions. Or am I just doing something wrong?
I believe the below query works, i havent tested!
INSERT INTO Test (b)
SELECT * FROM
(
SELECT 'z' FROM dual
UNION ALL
SELECT 'zz' FROM dual
);
Not sure, if it helps you any way.
For, GENERATED ALWAYS AS IDENTITY Oracle internally uses a Sequence only. And the options on general Sequence applies on this as well.
NEXTVAL is used to fetch the next available sequence, and obviously it is a pseudocolumn.
The below is from Oracle
You cannot use CURRVAL and NEXTVAL in the following constructs:
A subquery in a DELETE, SELECT, or UPDATE statement
A query of a view or of a materialized view
A SELECT statement with the DISTINCT operator
A SELECT statement with a GROUP BY clause or ORDER BY clause
A SELECT statement that is combined with another SELECT statement with the UNION, INTERSECT, or MINUS set operator
The WHERE clause of a SELECT statement
DEFAULT value of a column in a CREATE TABLE or ALTER TABLE statement
The condition of a CHECK constraint
The subquery and SET operations rule above should answer your Question.
And for the reason for NULL, when pseudocolumn(eg. NEXTVAL) is used with a SET operation or any other rules mentioned above, the output is NULL, as Oracle couldnt extract them in effect with combining multiple selects.
Let us see the below query,
select rownum from dual
union all
select rownum from dual
the result is
ROWNUM
1
1

Bulk Update from one table to another

So I tried the bulk update in order to copy values from uemte_id column in pp_terminal table to uemte_id column (null at start) in mm_chip table. These two tables have no columns in common.This is what I used:
declare
type ue_tab is table of
pp_terminal.uemte_id%type;
ue_name ue_tab;
cursor c1 is select uemte_id from pp_terminal;
begin
open c1;
fetch c1 bulk collect into ue_name;
close c1;
-- bulk insert
forall indx in ue_name.first..ue_name.last
update mm_chip set uemte_id = ue_name(indx);
end;
/
And this is the error message I get:
Error report:
ORA-00001: unique constraint (DPOWNERA.IX_AK7_MM_CHIP) violated
ORA-06512: at line 13
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.
Do you see any obvious misstakes?
What you're trying to do is:
select a row from the first table
update every row in the second table with that value
select another row from the first table
update every row in the second table with that value
and so forth until the loop finishes
I'm guessing that's not what you really want to do. It's failing because you have a unique constraint so you're not allowed to have multiple rows in the second table with the same value.
Below is one way to update each row of one table based on the value of an arbitrary row in a second table, without reusing any rows from the second table. It would perform better if you could do it entirely in SQL, but I couldn't come up with a way to do that.
CREATE TABLE test4 AS
(SELECT LEVEL AS cola, CAST(NULL AS number) AS colb
FROM DUAL
CONNECT BY LEVEL <= 100);
CREATE TABLE test5 AS
(SELECT 100 + LEVEL AS colc
FROM DUAL
CONNECT BY LEVEL <= 99);
DECLARE
CURSOR cur_test4 IS
SELECT *
FROM test4
FOR UPDATE ;
CURSOR cur_test5 IS
SELECT * FROM test5;
r_test5 cur_test5%ROWTYPE;
BEGIN
OPEN cur_test5;
FOR r_test4 IN cur_test4 LOOP
FETCH cur_test5 INTO r_test5;
IF cur_test5%NOTFOUND THEN
EXIT;
END IF;
UPDATE test4
SET colb = r_test5.colc
WHERE CURRENT OF cur_test4;
END LOOP;
CLOSE cur_test5;
END;

Pattern to substitute for MERGE INTO Oracle syntax when not allowed

I have an application that uses the Oracle MERGE INTO... DML statement to update table A to correspond with some of the changes in another table B (table A is a summary of selected parts of table B along with some other info). In a typical merge operation, 5-6 rows (out of 10's of thousands) might be inserted in table B and 2-3 rows updated.
It turns out that the application is to be deployed in an environment that has a security policy on the target tables. The MERGE INTO... statement can't be used with these tables (ORA-28132: Merge into syntax does not support security policies)
So we have to change the MERGE INTO... logic to use regular inserts and updates instead. Is this a problem anyone else has run into? Is there a best-practice pattern for converting the WHEN MATCHED/WHEN NOT MATCHED logic in the merge statement into INSERT and UPDATE statements? The merge is within a stored procedure, so it's fine for the solution to use PL/SQL in addition to the DML if that is required.
Another way to do this (other than Merge) would be using two sql statements one for insert and one for update. The "WHEN MATCHED" and "WHEN NOT MATCHED" can be handled using joins or "in" Clause.
If you decide to take the below approach, it is better to run the update first (sine it only runs for the matching records) and then insert the non-Matching records. The Data sets would be the same either way, it just updates less number of records with the order below.
Also, Similar to the Merge, this update statement updates the Name Column even if the names in Source and Target match. If you dont want that, add that condition to the where as well.
create table src_table(
id number primary key,
name varchar2(20) not null
);
create table tgt_table(
id number primary key,
name varchar2(20) not null
);
insert into src_table values (1, 'abc');
insert into src_table values (2, 'def');
insert into src_table values (3, 'ghi');
insert into tgt_table values (1, 'abc');
insert into tgt_table values (2,'xyz');
SQL> select * from Src_Table;
ID NAME
---------- --------------------
1 abc
2 def
3 ghi
SQL> select * from Tgt_Table;
ID NAME
---------- --------------------
2 xyz
1 abc
Update tgt_Table tgt
set Tgt.Name =
(select Src.Name
from Src_Table Src
where Src.id = Tgt.id
);
2 rows updated. --Notice that ID 1 is updated even though value did not change
select * from Tgt_Table;
ID NAME
----- --------------------
2 def
1 abc
insert into tgt_Table
select src.*
from Src_Table src,
tgt_Table tgt
where src.id = tgt.id(+)
and tgt.id is null;
1 row created.
SQL> select * from tgt_Table;
ID NAME
---------- --------------------
2 def
1 abc
3 ghi
commit;
There could be better ways to do this, but this seems simple and SQL-oriented. If the Data set is Large, then a PL/SQL solution won't be as performant.
There are at least two options I can think of aside from digging into the security policy, which I don't know much about.
Process the records to merge row by row. Attempt to do the update, if it fails to update then insert, or vise versa, depending on whether you expect most records to need updating or inserting (ie optimize for the most common case that will reduce the number of SQL statements fired), eg:
begin
for row in (select ... from source_table) loop
update table_to_be_merged
if sql%rowcount = 0 then -- no row matched, so need to insert
insert ...
end if;
end loop;
end;
Another option may be to bulk collect the records you want to merge into an array, and then attempted to bulk insert them, catching all the primary key exceptions (I cannot recall the syntax for this right now, but you can get a bulk insert to place all the rows that fail to insert into another array and then process them).
Logically a merge statement has to check for the presence of each records behind the scenes anyway, and I think it is processed quite similarly to the code I posted above. However, merge will always be more efficient than coding it in PLSQL as it will be only 1 SQL call instead of many.

Resources