plsql: inserting Multiple rows from select and ignore duplicates - oracle

I am using oracle 10g with TOAD.
I need to insert lacs of records using INSERT FROM SELECT.
BEGIN
INSERT INTO MYTABLE(C1,C2,C3)
SELECT C1,C2,C3 FROM MYTABLE2 WHERE C1>100;
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN NULL;
COMMIT;
END;
Here, the problem , i am facing is , if this select queries return rows which is already exists in MYTABLE, THEN all transaction will be rolledback.
Is there a way to insert all non-existent rows ,ignoring duplicate rows and continuing with insertion of non-existent rows and then committing the transaction?

Use the Distinct Keyword
BEGIN
INSERT INTO MYTABLE(C1,C2,C3)
SELECT distinct C1,C2,C3 FROM MYTABLE2 WHERE C1>100;
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN NULL;
COMMIT;
END;

Instead of trying to handle the exception, you can avoid these rows in the first place, e.g., by using the minus operator:
INSERT INTO mytable (c1, c2, c3)
SELECT c1, c2, c3
FROM mytable2
WHERE c1 > 100;
MINUS
SELECT c1, c2, c3
FROM mytable
WHERE c1 > 100 -- not necessary, but should improve performance

There are many ways to do it. First of all, you can try something like this:
insert into mytable(c1, c2, c3)
select distinct c1, c2, c3 from mytable2 where c1 > 100
minus
select c1, c2, c3 from mytable;
Otherwise, you can use something like
insert into mytable(c1, c2, c3)
select c1, c2, c3 from mytable2 where c1 > 100
log errors into myerrtable reject limit unlimited;
And so on...
More detailed about error logging. Feauture introduced since 10g release 2.
SQL> create table garbage(id integer);
Table created
SQL> insert into garbage select rownum from dual connect by level <= 10;
10 rows inserted
SQL> insert into garbage values (3);
1 row inserted
SQL> insert into garbage values (5);
1 row inserted
SQL> create table uniq(id integer not null primary key);
Table created
SQL> insert into uniq select * from garbage;
ORA-00001: unique constraint (TEST.SYS_C0010568) violated
SQL> select count(*) from uniq;
COUNT(*)
----------
0
SQL> exec dbms_errlog.create_error_log('uniq', 'uniq_err');
PL/SQL procedure successfully completed
SQL> insert into uniq select * from garbage
2 log errors into uniq_err reject limit unlimited;
10 rows inserted
SQL> select * from uniq;
ID
---------------------------------------
1
2
3
4
5
6
7
8
9
10
10 rows selected
SQL> select ora_err_mesg$, id from uniq_err;
ORA_ERR_MESG$ ID
---------------------------------------------------------------------- --
ORA-00001: unique constraint (TEST.SYS_C0010568) violated 3
ORA-00001: unique constraint (TEST.SYS_C0010568) violated 5

Thought I would make this an answer, but it really depends on what your trying to achieve.
You can check to see if the data is already in table2 using:
INSERT INTO mytable2 (c1, c2, c3)
SELECT DISTINCT c1,c2,c3 FROM mytable t1
WHERE c1 > 100
AND NOT EXISTS
(select 1 from mytable2 t2 where t2.c1 = t1.c1 and t2.c2 = t1.c2 and t2.c3 = t1.c3);
or you can use a merge like this:
MERGE INTO mytable2 m2
USING (SELECT DISTINCT c1, c2, c3 FROM mytable) m1
ON (m1.c1 = m2.c1 and m1.c2 = m2.c2 and m1.c3 = m2.c3)
WHEN NOT MATCHED THEN INSERT (c1, c2, c3) VALUES (m1.c1, m1.c2, m1.c3)
where m1.c1 > 100;
In both examples, you will only insert unique rows into mytable2

Related

In Oracle SQL Developer, I am unable to create table due to duplicate columns into same table

I am unable to create table due to duplicate columns into same table.
There is duplicate column name called load_Tstamp. How to rename the column or any other alternative?
Create table New_County_code as (
Select a.*,b.LOAD_TSTMP as Load_time
from (
Select CONCAT( b.STATE_FIPS_CODE,a.ZIP_CNTY_CDE) AS "Final_County_Code",
a.*,
b.*
from mdmstggeo.T_USPS_DETAIL_RECORD#ODSDEV a
left join (
select *
from DSSCORP.T_STATE#ds31
where STATE_FIPS_CODE is not null) b
on ST_CPTL_BLDG_ZIP_CDE = DTL_ZIP_CDE
WHERE ds_batch_gid=1661
order by STATE_FIPS_CODE
)
)
Every column name in your target table must be unique, so use column aliases wherever needed to ensure that
SQL> create table t1 as
2 select a.*, b.*
3 from scott.dept a,
4 scott.dept b;
select a.*, b.*
*
ERROR at line 2:
ORA-00957: duplicate column name
SQL>
SQL> create table t1 as
2 select a.*, b.deptno c1, b.dname c2, b.loc c3
3 from scott.dept a,
4 scott.dept b;
Table created.
SQL>
SQL> desc t1
Name Null? Type
----------------------------------------------------------------------- -------- --------------
DEPTNO NUMBER(2)
DNAME VARCHAR2(14)
LOC VARCHAR2(13)
C1 NUMBER(2)
C2 VARCHAR2(14)
C3 VARCHAR2(13)

Oracle - Grouping Sets and Pipelined Table Functions (expected NUMBER got ROW)

I'm working on a report with summary logic using GROUPING SETS, but I'm getting this error:
SELECT c1, c2, c3, SUM(c4) AS MySum
FROM TABLE(get_data()) src
GROUP BY GROUPING SETS ((c1, c2, c3), (c1, c2), c1, c2, ());
-------------------------
ORA-00932: inconsistent datatypes: expected NUMBER got XXX.MYROW
00932. 00000 - "inconsistent datatypes: expected %s got %s"
It works fine when I only include c1 or c2 separately:
GROUP BY GROUPING SETS ((c1, c2, c3), (c1, c2), c1, ());
GROUP BY GROUPING SETS ((c1, c2, c3), (c1, c2), c2, ());
It also works fine when I source the query directly from the t1 table:
SELECT c1, c2, c3, SUM(c4) AS MySum
FROM t1 src
GROUP BY GROUPING SETS ((c1, c2, c3), (c1, c2), c1, c2, ());
What am I missing? I feel like it's something simple. Here's a simplified example of my setup:
-- Base table
CREATE TABLE t1 (c1 VARCHAR(10), c2 VARCHAR(10), c3 VARCHAR(10), c4 INTEGER);
-- Row type
CREATE TYPE myrow AS OBJECT (c1 VARCHAR(10), c2 VARCHAR(10), c3 VARCHAR(10), c4 INTEGER);
-- Table type
CREATE OR REPLACE TYPE mytable AS TABLE OF myrow;
-- Get data function
CREATE OR REPLACE FUNCTION get_mydata
RETURN mytable PIPELINED AS
BEGIN
FOR v_rec IN (
SELECT c1, c2, c3, c4
FROM t1
) LOOP
PIPE ROW (myrow(v_Rec.c1, v_Rec.c2, v_Rec.c3, v_Rec.c4));
END LOOP;
RETURN;
END;
DB version - 12.1.0
Update
Different error I get with my actual function (even with "materialize" hint):
ORA-22905: cannot access rows from a non-nested table item 22905.
00000 - "cannot access rows from a non-nested table item"
*Cause: attempt to access rows of an item whose type is not known
at parse time or that is not of a nested table type
*Action: use CAST to cast the item to a nested table type
I don't know why it won't work, but - see if this workaround helps (use a CTE and the materialize hint):
SQL> with test as
2 (select /*+ materialize */
3 c1, c2, c3, c4
4 from table(get_mydata()) src
5 )
6 select c1, c2, c3, sum(c4) as mysum
7 from test
8 group by grouping sets ((c1, c2, c3), (c1, c2), c1, c2, ());
C1 C2 C3 MYSUM
---------- ---------- ---------- ----------
1 2 3 4
1 4
2 4
4
1 2 4
SQL>
You can workaround the bug by using a non-pipelined function and BULK COLLECT:
CREATE OR REPLACE FUNCTION get_mydata2
RETURN mytable AS
v_data mytable;
BEGIN
SELECT myrow(c1, c2, c3, c4)
BULK COLLECT
INTO v_data
FROM t1;
RETURN v_data;
END;
/
I was able to reproduce your error in version 19c so it looks like you found a relatively big Oracle bug. In my experience, pipelined functions are generally buggier than regular functions and tend to be over-used. Pipelined functions can return data faster, but since your query is aggregating all the rows, you won't see that performance boost anyway.
The main problem with the non-pipelined function approach is that your session will need enough memory to store all of the results from the BULK COLLECT at once. That may not be feasible if you have to process millions of wide rows.

How to insert multiple cursor result set into one table

1 table having table structure-
create table tab_abc
( id varchar2(10),
inv_bfr varchar2(20),
desc varchar2(10),
inv_afr varchar2(10) );
I defined 2 cursor here as C1 & C2 ->
cursor C1 is select id, count(inv) AS "inv_bfr", desc from tab_a group by id, desc;
cursor C2 is select count(inv) AS "inv_afr" from tab_a;
Result set of both cursor C1 & C2 will insert into table tab_abc. Cursor C1 will open before one DML operation perform & cursor C2 will open after DML operation perform. Could you please help me can i use OPEN CURSOR THEN FETCH process would be good or FOR CURSOR LOOP INSERT INTO TABLE process.
You don't need to use cursors (or collections, more realistically), or even any PL/SQL here. You can insert data into the table before your 'DML operaton perform' step, and then update if afterwards, e.g. with a merge:
-- initial population
insert into tab_abc (id, inv_bfr, descr, inv_afr)
select id, count(*) as inv_bfr, descr, 0 as inv_after
from tab_a
group by id, descr;
-- intermediate DML operation
-- post-DML update
merge into tab_abc t
using (
select id, 0 as inv_bfr, descr, count(*) as inv_afr
from tab_a
group by id, descr
) afr
on (afr.id = t.id and afr.descr = t.descr)
when matched then
update set inv_afr = afr.inv_afr
when not matched then
insert (id, inv_bfr, descr, inv_afr)
values (afr.id, afr.inv_bfr, afr.descr, afr.inv_afr);
You can wrap all of that in a PL/SQL block if you need to for other reasons, of course.
db<>fiddle demo with a few made-up rows.

Selecting duplicate rows from a table (ignoring the primary key)

I have an Oracle table where I want to find out if there are any duplicate rows (i.e. where all column values are equal). The problem is that the rows have unique primary keys so I want to exclude them since they are basically preventing me doing this.
Is there a way to ignore the primary key when doing such a task (instead of listing all columns except the primary key column) so that I can find out the duplicate rows?
No, just list all columns except the primary key columns in the GROUP BY clause:
CREATE TABLE mytable (
pk NUMBER PRIMARY KEY,
c1 NUMBER NOT NULL,
c2 NUMBER
);
INSERT INTO mytable (pk, c1, c2) VALUES (100, 1, 1);
INSERT INTO mytable (pk, c1, c2) VALUES (101, 1, 1);
INSERT INTO mytable (pk, c1, c2) VALUES (102, 2, 1);
INSERT INTO mytable (pk, c1, c2) VALUES (103, 2, null);
INSERT INTO mytable (pk, c1, c2) VALUES (104, 2, null);
SELECT c1, c2
FROM mytable
GROUP BY c1, c2
HAVING COUNT(*) > 1;
C1 C2
----- -----
1 1
2 (null)
To find out the non-primary key columns, you could use the following query. For most tables it will be quicker to type the columns instead of pasting/running the query:
SELECT column_name
FROM user_tab_columns co
WHERE co.table_name = 'MYTABLE'
AND NOT EXISTS (
SELECT *
FROM user_constraints pk
JOIN user_cons_columns pc USING (owner, constraint_name)
WHERE pk.table_name = co.table_name
AND constraint_type='P'
AND co.column_name = pc.column_name)
ORDER BY co.column_id;
You're going to have to list the other columns explicitly.
Potentially, you could use dynamic SQL to generate the query that you want. But that is unlikely to be terribly helpful if this is just for a single table. If you were trying to automate a process of comparing dozens or hundreds of tables, a dynamic SQL approach would potentially be easier to manage.

insert and select in one query default values

I use following query to insert a new row:
insert into table1 (c1, c2, c3) (select c1, c2, c3 from table2 where some_condition)
This works finely if there is a row in table2 that satisfies some_condition. But if there are no rows, nothing is inserted.
Are there any way to specify default values to insert if select returns empty result set? I want to do that in one sql query.
This isn't very pretty, but it does what you want, You'd need to test with your environment to see if it performs well enough
SQL> drop table so_tgt;
Table dropped.
SQL>
SQL> create table so_src (
2 c1 varchar2(6)
3 ,c2 varchar2(6)
4 ,c3 varchar2(6)
5 );
Table created.
SQL>
SQL> insert into so_src values ( 'foo','bar','moo' );
1 row created.
SQL>
SQL> create table so_tgt as select * from so_src where 1 = 0;
Table created.
SQL>
SQL> /* Test for existing row insert */
SQL> insert into so_tgt
2 with x as ( select s.*, 1 as r
3 from so_src s
4 where c1='foo'
5 union
6 select 'x','y','z',0 as r /* DEFAULT VALUES */
7 from dual )
8 select c1,c2,c3
9 from x
10 where r = ( select max(r) from x ) ;
1 row created.
SQL>
SQL> select * from so_tgt;
C1 C2 C3
------ ------ ------
foo bar moo
SQL> truncate table so_tgt;
Table truncated.
SQL>
SQL> /* Test for default row insert */
SQL> insert into so_tgt
2 with x as ( select s.*, 1 as r
3 from so_src s
4 where c1='far'
5 union
6 select 'x','y','z',0 as r /* DEFAULT VALUES */
7 from dual )
8 select c1,c2,c3
9 from x
10 where r = ( select max(r) from x ) ;
1 row created.
SQL>
SQL> select * from so_tgt;
C1 C2 C3
------ ------ ------
x y z
SQL> truncate table so_tgt ;
Table truncated.
The quick and dirty way, if you don't mind repeating some_condition and where some_condition doesn't depend on the values in table2 is:
insert into table1 (c1,c2,c3)
select c1, c2, c3 from table2 where some_condition
union select defaultvalue1, defaultvalue2, defaultvalue3 from dual where not (some_condition)
If some_condition does depend on values in table2, then you can do (untested):
insert into table1 (c1,c2,c3)
select nvl(t2.c1, defaultvalue1), nvl(t2.c2, defaultvalue2), nvl(t2.c2, defaultvalue3)
from dual left join (select c1,c2,c3 from table2 where some_condition) t2
on 1 = 1
If I'm right, this query will always return at least one row, but if no rows showed up on the right side, then the t2 values will all be returned as null and so the nvl can be used to provide your default values.
Edit: Small caveat. This assumes that the values returned from table2 will not be null or that if they are, you want the default values.

Resources