insert and select in one query default values - oracle

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.

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)Correlated subquery usage

Subquery query1 below works fine.
But when I put equi condition in -sort of- nested clause like query2, it shows error ORA-00904.
Is this wrong usage of correlated subquery or it is because of other reason?
--Query1: It shows expected result.
SELECT
O.ENAME
O,SAL
,(SELECT COUNT(*)
FROM SCOTT.EMP I
WHERE I.SAL>O.SAL --correlated to outer
) AS RESULT
from SCOTT.EMP O;
--Query2:ORA-00904: "O"."SAL": invalid identifier shows. How to modify to use correlated subquery?
SELECT
O.ENAME
O,SAL
,(
WITH TEMP AS
(
SELECT COUNT(*)
FROM SCOTT.EMP I
WHERE I.SAL>O.SAL --I have put equi condistion here
)
SELECT * FROM TEMP
) AS RESULT
from SCOTT.EMP O;
I believe the second option is a wrong use of correlated subqueries, not because of the comparison, but for the use of the with clause. I would like to remember that you should avoid correlated subqueries as much as possible.
The WITH clause, or subquery factoring clause, may be processed as an inline view or resolved as a temporary table. The advantage of the latter is that repeated references to the subquery may be more efficient as the data is easily retrieved from the temporary table, rather than being requeried by each reference.
In your third column of your second query you want to get the result from the inline view. The problem is that parsing of the inline view is done independently and therefore cannot have references to anything in the outer query.
SQL> create table emp ( ename varchar2(10) , sal number ) ;
Table created.
SQL> insert into emp values ( 'AAA' , 1000 ) ;
insert into emp values ( 'BBB' , 1000 ) ;
insert into emp values ( 'CCC' , 1000 ) ;
insert into emp values ( 'DDD' , 1000 ) ;
1 row created.
SQL> SQL>
1 row created.
SQL> SQL>
1 row created.
SQL> SQL>
1 row created.
SQL> select * from emp ;
ENAME SAL
---------- ----------
AAA 1000
BBB 1000
CCC 1000
DDD 1000
To write the query with inline view, the filter must be done in the outer query
SELECT
O.ENAME
O,SAL
,(
WITH TEMP AS
(
SELECT * FROM EMP
)
SELECT count(*) FROM TEMP t WHERE t.SAL>O.SAL
) AS RESULT
from EMP O;
O SAL RESULT
---------- ---------- ----------
AAA 1000 0
BBB 1000 0
CCC 1000 0
DDD 1000 0
It has been explained that the second query does not uses with correctly.
Let me suggest, however, that your query can be simpler and more efficiently phrased. For each employee, you want to count how many employees have a greater salary. Window functions are the way to go here:
select e.*, rank() over(order by salary desc) - 1 result
from scott.emp e

PL/SQL Regex search based on both characters and digits

I have a table TA which has a column inv_ref with has data as below.
inv_ref
----------
MX/3280/20
CT/3281/20
CT/3109/20
MX/3272/20
RF/3275/20
My requirement is to fetch whereas middle 4 digit number of inv_ref between 3270 to 3299 also begin with either MX and CT.
select * from TA where regexp_like(inv_ref, 'CT/32[7-9][0-9]/20')
Above query only returns CT, how can return both CT and MX related values omitting RF?
You can use:
SELECT *
FROM TA
WHERE REGEXP_LIKE(inv_ref, '^(CT|MX)/32[7-9][0-9]/20$')
Then if you have the test data:
CREATE TABLE TA ( inv_ref, is_valid ) AS
SELECT 'MX/3280/20', 'Valid' FROM DUAL UNION ALL
SELECT 'CT/3281/20', 'Valid' FROM DUAL UNION ALL
SELECT 'CT/3109/20', 'Invalid, number too low' FROM DUAL UNION ALL
SELECT 'MX/3272/20', 'Valid' FROM DUAL UNION ALL
SELECT 'RF/3275/20', 'Invalid, wrong start' FROM DUAL UNION ALL
SELECT 'CX/3299/20', 'Invalid, wrong start' FROM DUAL UNION ALL
SELECT 'MT/3270/20', 'Invalid, wrong start' FROM DUAL UNION ALL
SELECT 'ACT/3270/20', 'Invalid, wrong start' FROM DUAL;
This outputs:
INV_REF | IS_VALID
:--------- | :-------
MX/3280/20 | Valid
CT/3281/20 | Valid
MX/3272/20 | Valid
db<>fiddle here
It would be much easier to add virtual generated columns:
alter table your_table add (
P1 generated always as (regexp_substr(inv_ref,'^[^/]+')),
P2 generated always as (to_number(regexp_substr(inv_ref,'/(\d+)/',1,1,null,1))),
P3 generated always as (regexp_substr(inv_ref,'[^/]+$'))
);
In this case you can use standard predicates on those columns. Moreover, you can even create indexes on those columns.
Full test case:
CREATE TABLE YOUR_TABLE ( inv_ref ) AS
SELECT 'MX/3280/20' FROM DUAL UNION ALL
SELECT 'CT/3281/20' FROM DUAL UNION ALL
SELECT 'CT/3109/20' FROM DUAL UNION ALL
SELECT 'MX/3272/20' FROM DUAL UNION ALL
SELECT 'RF/3275/20' FROM DUAL UNION ALL
SELECT 'CX/3299/20' FROM DUAL UNION ALL
SELECT 'MT/3270/20' FROM DUAL UNION ALL
SELECT 'ACT/3270/20' FROM DUAL;
alter table your_table add (
P1 generated always as (regexp_substr(inv_ref,'^[^/]+')),
P2 generated always as (to_number(regexp_substr(inv_ref,'/(\d+)/',1,1,null,1))),
P3 generated always as (regexp_substr(inv_ref,'[^/]+$'))
);
select * from your_table
where p1 IN ('CT', 'MX')
and p2 BETWEEN 3270 and 3299;
Result:
MX/3280/20 MX 3280 20
CT/3281/20 CT 3281 20
MX/3272/20 MX 3272 20
You could use
SQL> create table my_test ( inv_ref varchar2(100) ) ;
SQL> insert into my_test values ( 'MX/3280/20') ;
SQL> insert into my_test values ( 'CD/3281/20') ;
SQL> insert into my_test values ( 'CD/3109/20') ;
SQL> insert into my_test values ( 'MX/3272/20') ;
SQL> insert into my_test values ( 'RF/3275/20') ;
Table created.
SQL> SQL> SQL>
1 row created.
SQL> SQL>
1 row created.
SQL> SQL>
1 row created.
SQL> SQL>
1 row created.
SQL> SQL>
1 row created.
SQL> commit ;
Commit complete.
Two options ( in my case I use CD instead of CT ). This option will work as long as the strings are limited to the example. If you would have other combination it won't , as CD|MX means C or D or M or X ). See comments to the answer. #MTO, Thanks for your comments.
SQL> select * from my_test where regexp_like(inv_ref, '[CD|MX]/32[7-9][0-9]/20')
INV_REF
--------------------------------------------------------------------------------
MX/3280/20
CD/3281/20
MX/3272/20
SQL> select * from my_test where regexp_like(inv_ref, 'CD/32[7-9][0-9]/20') or
regexp_like(inv_ref, 'MX/32[7-9][0-9]/20')
INV_REF
--------------------------------------------------------------------------------
MX/3280/20
CD/3281/20
MX/3272/20
You can use the values separated by | (or) as follows:
select * from TA where regexp_like(inv_ref, '^(CT|MX)/32[7-9][0-9]/20');
db<>fiddle

Oracle update from random on another table

I have some fields in table1 to update with random values from some fields in table2.
I have to random into rows of table2 and update each rows of table1 with the same rows values of table2.
Here is my SQL code, but it doesn't work.
update owner.table1 t1
set (t1.adress1, t1.zip_code, t1.town) = (select t2.adress, t2.zip_code, t2.town
from table1 t2
where id = trunc(dbms_random.value(1,20000)))
Result: all rows are updated with the same values, like no random on table 2 rows
How about switching to analytic ROW_NUMBER function? It doesn't really create a random value, but might be good enough.
Here's an example: first, create test tables and insert some data:
SQL> create table t1 (id number,address varchar2(20), town varchar2(10));
Table created.
SQL> create table t2 (id number, address varchar2(20), town varchar2(10));
Table created.
SQL> insert into t1
2 select 1, 'Ilica 20', 'Zagreb' from dual union all
3 select 2, 'Petrinjska 30', 'Sisak' from dual union all
4 select 3, 'Stradun 12', 'Dubrovnik' from dual;
3 rows created.
SQL> insert into t2
2 select 1, 'Pavelinska 15', 'Koprivnica' from dual union all
3 select 2, 'Baščaršija 11', 'Sarajevo' from dual union all
4 select 3, 'Riva 22', 'Split' from dual;
3 rows created.
SQL> select * From t1 order by id;
ID ADDRESS TOWN
---------- -------------------- ----------
1 Ilica 20 Zagreb
2 Petrinjska 30 Sisak
3 Stradun 12 Dubrovnik
SQL> select * From t2 order by id;
ID ADDRESS TOWN
---------- -------------------- ----------
1 Pavelinska 15 Koprivnica
2 Baščaršija 11 Sarajevo
3 Riva 22 Split
Update t1 with rows from t2:
SQL> update t1 set
2 (t1.address, t1.town) =
3 (select x.address, x.town
4 from (select row_number() over (order by address) id, t2.address, t2.town
5 from t2
6 ) x
7 where x.id = t1.id);
3 rows updated.
SQL> select * From t1 order by id;
ID ADDRESS TOWN
---------- -------------------- ----------
1 Baščaršija 11 Sarajevo
2 Pavelinska 15 Koprivnica
3 Riva 22 Split
SQL>

plsql: inserting Multiple rows from select and ignore duplicates

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

Resources