I have a stored procedure which looks like this:
BEGIN
INSERT INTO result_table
(SELECT (...) FROM query_table);
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN
NULL;
END;
I'm doing it in a loop which passes multiple parameters to the SELECT statement and in some cases some of the values might duplicate that is why I have to catch the DUP_VAL_ON_INDEX exception.
My question is that if the SELECT statement returns more rows and only one from them exists already in *result_table*, f. ex.
1 'A'
2 'B'
3 'C'
And first row (1 'A') would already be in the table, would other rows which don't exist (second and third from case above) be inserted? Or none of them would be inserted at all?
I'm afraid that none of them would be inserted (and my test case partially confirms that)... If so, what option do I have to achieve desired bahavior? Is there a good way to insert the rows that don't violate the primary key using the construction above?
You can use the MERGE statement. Insert the records if they don't exist and do nothing if they already exist.
http://psoug.org/reference/merge.html
You are right, if one record violates constraint, none will be inserted.
I'd do
INSERT INTO result_table
(SELECT (...) FROM query_table a WHERE NOT EXISTS
(SELECT NULL FROM result_table b WHERE b.b_unique_key = a.b_unique_key)
)
Another option is to use error logging
INSERT INTO result_table
SELECT ... FROM query_table
LOG ERRORS INTO err$_dest ('INSERT') REJECT LIMIT UNLIMITED;
Note: you have to create error table prior to run this query.
If you're using 11g, then you can use the ignore_row_on_dupkey_index hint to suppress the errors:
create table tab (id integer);
alter table tab add constraint tab_pk primary key (id);
insert into tab
select rownum from dual connect by level <= 1;
1 rows inserted.
ID
----------
1
SELECT * FROM tab;
insert into tab
select rownum from dual connect by level <= 3;
SQL Error: ORA-00001: unique constraint (CAM_OWNER.TAB_PK) violated
insert /*+ ignore_row_on_dupkey_index(tab, tab_pk) */into tab
select rownum from dual connect by level <= 3;
SELECT * FROM tab;
2 rows inserted.
ID
----------
1
2
3
Related
This is odd or I am too tired but I dont get it (but I guess it is obvious). Here is the code :
for myVariable1 in (select distinct id from myTable1)
loop
begin
select myColumn into AVariable from myTable2 where id = myVariable1.id
exception
When TOO_MANY_ROWS then
dbms_output.put_line('TOO_MANY_ROWS for ' || myVariable1.id);
end;
end loop;
Quite simple and every id selected in myTable2 triggers a Too_MANY_ROWS exception though it is not the case.
here is my log:
TOO_MANY_ROWS for 7500123
TOO_MANY_ROWS for 5900123
Here is the result of the queries :
select myColumn from myTable2 where id = '7500123'
1 row returned
select myColumn from myTable2 where id = '5900123'
1 row returned.
Can somebody explain it ?
I think that the id field contains the blank spaces.
For example:
with table1 as (
select '7500123' as id from dual union
select '7500123 ' as id from dual
)
select *
from table1
where id = '7500123';
ID
--------
7500123
And then when you run the query control give:
select myColumn from myTable2 where trim(id) = '7500123';
2 row returned
Please me back if it is the cause.
Thank you
Do you have an unique constraint on table2.id ?
If not, it may be a problem inside the transaction where you execute the loop.
If you insert by mistake another row with the same id, you get the exception, the transaction is rollbacked and then when checking the uniqueness outside of the procedure you don't see the problem anymore.
I must execute multiple insert from azure data factory to Oracle and I am using the following statement
INSERT ALL INTO TABLENAME (CTCPIA,CTAN01,CTAN02,CTCRCD,CTCRR,CTAAN05,CTAAN04,CTCRCA,CTCRRB,CTDL011,CTDSG,CTCSIC,CTCPIL,CTEDDJ,CTUSER,CTPID,CTMKEY,CTUPMJ,CTTDAY) SELECT 'XXXX','31028775300.00','31028775300.00','COP','1','0.00','0.00','COP','0.00','Published','Executing','XXXX','XXXX','123059','LAKE-CHEC','XXX','XXX','122097','165729' FROM dual WHERE NOT EXISTS (SELECT 1 FROM TABLENAME WHERE CTCPIA='XXXX') SELECT * FROM dual;
I am adding the lines WHERE NOT EXISTS (SELECT 1 FROM TABLENAME WHERE CTCPIA='XXXX') to validate that no duplicates are inserted, however from datafactory it is throwing me the following error
How could I validate duplicates within the INSERT ALL statement? since I have to execute a lot of INSERT statements.
Thanks a lot.
I would suggest to refer documentation for INSERT ALL (Section: "The multitable insert statement looks like this")
I performed an insert to explain the scenario, see if that matches your use-case.
Current state of a table -
select * from test;
ID NAME
----- ----------
1 adsa
2 fa
2 xxx
Inserting 'ALL' to table TEST to avoid duplicates
based on condition (value NOT EXISTS)- No rows are inserted.
insert all
into test (id, name)
values (id+1, name||'a')
into test (id, name)
values (id+2, name||'b')
into test (id, name)
values (id+3, name||'c')
select 1 as id,'any name' as name from dual
where NOT EXISTS
(select 1 from test where name='xxx');
0 rows created.
Inserting 'ALL' to table TEST
based on condition (value EXISTS)-
insert all
into test (id, name)
values (id+1, name||'a')
into test (id, name)
values (id+2, name||'b')
into test (id, name)
values (id+3, name||'c')
select 1 as id,'any name' as name from dual
where EXISTS
(select 1 from test where name='xxx');
3 rows created.
select * from test;
ID NAME
----- ----------
1 adsa
2 fa
2 xxx
2 any namea
3 any nameb
4 any namec
CREATE OR REPLACE TYPE nvarchar2_list_type AS TABLE OF NVARCHAR2(100);
CREATE TABLE test_table(
id number primary key,
cars_list nvarchar2_list_type
)
NESTED TABLE cars_list STORE AS cars_list_storage_table;
insert into test_table(id, cars_list)
values(1, nvarchar2_list_type( 'AUDI', 'MERCEDES') );
All above operations completed success, 1 rows inserted in table test_table, now i write this function:
create or replace function get_cnt
return number
as
ret_val number;
begin
SELECT cars_list.COUNT
INTO ret_val
from test_table where id = 1;
return ret_val;
end;
This gives error: ORA-00904: "CARS_LIST"."COUNT": invalid identifier
Tell please what is wrong here?
As I know, COUNT method must be used just so (from here)
No, you cannot use count method in this situation. You have SQL nested table at hand, count method is used only with PL/SQL collections.
To count number of nested table's elements you can either unnest that nested table or use scalar sub-query:
Unnesting:
SQL> select id
2 , count(*) as cnt
3 from test_table t
4 cross join table(t.cars_list)
5 group by id
6 ;
ID CNT
---------- ----------
1 2
Scalar sub-query:
SQL> select id
2 , (select count(column_value)
3 from table(t.cars_list)) as cnt
4 from test_table t
5 ;
ID CNT
---------- ----------
1 2
Use
Select
Cardinality(cars_list) from test_table
I can't explain why that doesn't work, but this does:
select (select count(*) from table(cars_list))
into ret_val
from test_table
where id = 1;
Oracle is expecting column name or function in its select list, but what you are giving is collection build in method that operates on collections only.
You can achieve the same using scalar sub query
SELECT (select count(1) from table(cars_list)) as "COUNT"
FROM test_table
WHERE id = 1;
I need to update a query so that it checks that a duplicate entry does not exist before insertion. In MySQL I can just use INSERT IGNORE so that if a duplicate record is found it just skips the insert, but I can't seem to find an equivalent option for Oracle. Any suggestions?
If you're on 11g you can use the hint IGNORE_ROW_ON_DUPKEY_INDEX:
SQL> create table my_table(a number, constraint my_table_pk primary key (a));
Table created.
SQL> insert /*+ ignore_row_on_dupkey_index(my_table, my_table_pk) */
2 into my_table
3 select 1 from dual
4 union all
5 select 1 from dual;
1 row created.
Check out the MERGE statement. This should do what you want - it's the WHEN NOT MATCHED clause that will do this.
Do to Oracle's lack of support for a true VALUES() clause the syntax for a single record with fixed values is pretty clumsy though:
MERGE INTO your_table yt
USING (
SELECT 42 as the_pk_value,
'some_value' as some_column
FROM dual
) t on (yt.pk = t.the_pke_value)
WHEN NOT MATCHED THEN
INSERT (pk, the_column)
VALUES (t.the_pk_value, t.some_column);
A different approach (if you are e.g. doing bulk loading from a different table) is to use the "Error logging" facility of Oracle. The statement would look like this:
INSERT INTO your_table (col1, col2, col3)
SELECT c1, c2, c3
FROM staging_table
LOG ERRORS INTO errlog ('some comment') REJECT LIMIT UNLIMITED;
Afterwards all rows that would have thrown an error are available in the table errlog. You need to create that errlog table (or whatever name you choose) manually before running the insert using DBMS_ERRLOG.CREATE_ERROR_LOG.
See the manual for details
I don't think there is but to save time you can attempt the insert and ignore the inevitable error:
begin
insert into table_a( col1, col2, col3 )
values ( 1, 2, 3 );
exception when dup_val_on_index then
null;
end;
/
This will only ignore exceptions raised specifically by duplicate primary key or unique key constraints; everything else will be raised as normal.
If you don't want to do this then you have to select from the table first, which isn't really that efficient.
Another variant
Insert into my_table (student_id, group_id)
select distinct p.studentid, g.groupid
from person p, group g
where NOT EXISTS (select 1
from my_table a
where a.student_id = p.studentid
and a.group_id = g.groupid)
or you could do
Insert into my_table (student_id, group_id)
select distinct p.studentid, g.groupid
from person p, group g
MINUS
select student_id, group_id
from my_table
A simple solution
insert into t1
select from t2
where not exists
(select 1 from t1 where t1.id= t2.id)
This one isn't mine, but came in really handy when using sqlloader:
create a view that points to your table:
CREATE OR REPLACE VIEW test_view
AS SELECT * FROM test_tab
create the trigger:
CREATE OR REPLACE TRIGGER test_trig
INSTEAD OF INSERT ON test_view
FOR EACH ROW
BEGIN
INSERT INTO test_tab VALUES
(:NEW.id, :NEW.name);
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN NULL;
END test_trig;
and in the ctl file, insert into the view instead:
OPTIONS(ERRORS=0)
LOAD DATA
INFILE 'file_with_duplicates.csv'
INTO TABLE test_view
FIELDS TERMINATED BY ','
(id, field1)
How about simply adding an index with whatever fields you need to check for dupes on and say it must be unique? Saves a read check.
yet another "where not exists"-variant using dual...
insert into t1(id, unique_name)
select t1_seq.nextval, 'Franz-Xaver' from dual
where not exists (select 1 from t1 where unique_name = 'Franz-Xaver');
What is the easiest way to INSERT a row if it doesn't exist, in PL/SQL (oracle)?
I want something like:
IF NOT EXISTS (SELECT * FROM table WHERE name = 'jonny') THEN
INSERT INTO table VALUES ("jonny", null);
END IF;
But it's not working.
Note: this table has 2 fields, say, name and age. But only name is PK.
INSERT INTO table
SELECT 'jonny', NULL
FROM dual -- Not Oracle? No need for dual, drop that line
WHERE NOT EXISTS (SELECT NULL -- canonical way, but you can select
-- anything as EXISTS only checks existence
FROM table
WHERE name = 'jonny'
)
Assuming you are on 10g, you can also use the MERGE statement. This allows you to insert the row if it doesn't exist and ignore the row if it does exist. People tend to think of MERGE when they want to do an "upsert" (INSERT if the row doesn't exist and UPDATE if the row does exist) but the UPDATE part is optional now so it can also be used here.
SQL> create table foo (
2 name varchar2(10) primary key,
3 age number
4 );
Table created.
SQL> ed
Wrote file afiedt.buf
1 merge into foo a
2 using (select 'johnny' name, null age from dual) b
3 on (a.name = b.name)
4 when not matched then
5 insert( name, age)
6* values( b.name, b.age)
SQL> /
1 row merged.
SQL> /
0 rows merged.
SQL> select * from foo;
NAME AGE
---------- ----------
johnny
If name is a PK, then just insert and catch the error. The reason to do this rather than any check is that it will work even with multiple clients inserting at the same time. If you check and then insert, you have to hold a lock during that time, or expect the error anyway.
The code for this would be something like
BEGIN
INSERT INTO table( name, age )
VALUES( 'johnny', null );
EXCEPTION
WHEN dup_val_on_index
THEN
NULL; -- Intentionally ignore duplicates
END;
I found the examples a bit tricky to follow for the situation where you want to ensure a row exists in the destination table (especially when you have two columns as the primary key), but the primary key might not exist there at all so there's nothing to select.
This is what worked for me:
MERGE INTO table1 D
USING (
-- These are the row(s) you want to insert.
SELECT
'val1' AS FIELD_A,
'val2' AS FIELD_B
FROM DUAL
) S ON (
-- This is the criteria to find the above row(s) in the
-- destination table. S refers to the rows in the SELECT
-- statement above, D refers to the destination table.
D.FIELD_A = S.FIELD_A
AND D.FIELD_B = S.FIELD_B
)
-- This is the INSERT statement to run for each row that
-- doesn't exist in the destination table.
WHEN NOT MATCHED THEN INSERT (
FIELD_A,
FIELD_B,
FIELD_C
) VALUES (
S.FIELD_A,
S.FIELD_B,
'val3'
)
The key points are:
The SELECT statement inside the USING block must always return rows. If there are no rows returned from this query, no rows will be inserted or updated. Here I select from DUAL so there will always be exactly one row.
The ON condition is what sets the criteria for matching rows. If ON does not have a match then the INSERT statement is run.
You can also add a WHEN MATCHED THEN UPDATE clause if you want more control over the updates too.
Using parts of #benoit answer, I will use this:
DECLARE
varTmp NUMBER:=0;
BEGIN
-- checks
SELECT nvl((SELECT 1 FROM table WHERE name = 'john'), 0) INTO varTmp FROM dual;
-- insert
IF (varTmp = 1) THEN
INSERT INTO table (john, null)
END IF;
END;
Sorry for I don't use any full given answer, but I need IF check because my code is much more complex than this example table with name and age fields. I need a very clear code. Well thanks, I learned a lot! I'll accept #benoit answer.
In addition to the perfect and valid answers given so far, there is also the ignore_row_on_dupkey_index hint you might want to use:
create table tq84_a (
name varchar2 (20) primary key,
age number
);
insert /*+ ignore_row_on_dupkey_index(tq84_a(name)) */ into tq84_a values ('Johnny', 77);
insert /*+ ignore_row_on_dupkey_index(tq84_a(name)) */ into tq84_a values ('Pete' , 28);
insert /*+ ignore_row_on_dupkey_index(tq84_a(name)) */ into tq84_a values ('Sue' , 35);
insert /*+ ignore_row_on_dupkey_index(tq84_a(name)) */ into tq84_a values ('Johnny', null);
select * from tq84_a;
The hint is described on Tahiti.
you can use this syntax:
INSERT INTO table_name ( name, age )
select 'jonny', 18 from dual
where not exists(select 1 from table_name where name = 'jonny');
if its open an pop for asking as "enter substitution variable" then use this before the above queries:
set define off;
INSERT INTO table_name ( name, age )
select 'jonny', 18 from dual
where not exists(select 1 from table_name where name = 'jonny');
You should use Merge:
For example:
MERGE INTO employees e
USING (SELECT * FROM hr_records WHERE start_date > ADD_MONTHS(SYSDATE, -1)) h
ON (e.id = h.emp_id)
WHEN MATCHED THEN
UPDATE SET e.address = h.address
WHEN NOT MATCHED THEN
INSERT (id, address)
VALUES (h.emp_id, h.address);
or
MERGE INTO employees e
USING hr_records h
ON (e.id = h.emp_id)
WHEN MATCHED THEN
UPDATE SET e.address = h.address
WHEN NOT MATCHED THEN
INSERT (id, address)
VALUES (h.emp_id, h.address);
https://oracle-base.com/articles/9i/merge-statement
CTE and only CTE :-)
just throw out extra stuff. Here is almost complete and verbose form for all cases of life. And you can use any concise form.
INSERT INTO reports r
(r.id, r.name, r.key, r.param)
--
-- Invoke this script from "WITH" to the end (";")
-- to debug and see prepared values.
WITH
-- Some new data to add.
newData AS(
SELECT 'Name 1' name, 'key_new_1' key FROM DUAL
UNION SELECT 'Name 2' NAME, 'key_new_2' key FROM DUAL
UNION SELECT 'Name 3' NAME, 'key_new_3' key FROM DUAL
),
-- Any single row for copying with each new row from "newData",
-- if you will of course.
copyData AS(
SELECT r.*
FROM reports r
WHERE r.key = 'key_existing'
-- ! Prevent more than one row to return.
AND FALSE -- do something here for than!
),
-- Last used ID from the "reports" table (it depends on your case).
-- (not going to work with concurrent transactions)
maxId AS (SELECT MAX(id) AS id FROM reports),
--
-- Some construction of all data for insertion.
SELECT maxId.id + ROWNUM, newData.name, newData.key, copyData.param
FROM copyData
-- matrix multiplication :)
-- (or a recursion if you're imperative coder)
CROSS JOIN newData
CROSS JOIN maxId
--
-- Let's prevent re-insertion.
WHERE NOT EXISTS (
SELECT 1 FROM reports rs
WHERE rs.name IN(
SELECT name FROM newData
));
I call it "IF NOT EXISTS" on steroids. So, this helps me and I mostly do so.