subquery inside INSERT statement - oracle

I just recently found out that subqueries are not allowed in INSERT statements that are inside stored procedures. This is my script:
begin
execute immediate 'truncate table itcustadm.GL_DTPJ_TEST2';
insert into GL_DTPJ_TEST2
(rule_no,
posted_by_user_id,
transaction_id,
transaction_sr_no,
dr_amount,
cr_amount,
tran_crncy_code,
bkdt_tran_flg,
bank_desc
)
select
tq.rule_no,
tq.posted_by_user_id,
tq.transaction_id,
tq.transaction_sr_no,
tq.dr_amount,
tq.cr_amount,
tq.tran_crncy_code,
tq.bkdt_tran_flg,
(select ent.bank_desc from crmuser.end ent where ent.bank_id = gam.bank_id);
But since the (select ent.bank_desc from crmuser.end ent where ent.bank_id = gam.bank_id) at the bottom of the SELECT statement is not allowed by Oracle, what's the best way to accomplish this?
I actually have this code right before the INSERT statement, but I don't know how to exactly use it:
get_bank_desc := '(select ent.bank_desc from crmuser.end ent ' ||
'where ent.bank_id = gam.bank_id)';

I am not sure what you are exactly trying for, but below code may be useful for you, you can achieve inserting a SubQuery output into a table using below query sample, but make sure output of the SubQuery is a single row o/p, so that you can escape from "ORA-01427: single-row SubQuery returns more than one row" ERROR.
insert into test_ins1
values(1,(SELECT COL2 FROM TEST_INS WHERE COL1=1 ));
Even then you can use rownum in where condition and take the single value.
Please let me know in case of any doubts

declare
bank_desc_temp bank_desk_type; /* the type defined in crmuser.ent for bank_desc*/
begin
select ent.bank_desc into bank_desc_temp from crmuser.end ent where ent.bank_id = gam.bank_id;
execute immediate 'truncate table itcustadm.GL_DTPJ_TEST2';
insert into GL_DTPJ_TEST2
(rule_no,
posted_by_user_id,
transaction_id,
transaction_sr_no,
dr_amount,
cr_amount,
tran_crncy_code,
bkdt_tran_flg,
bank_desc
)
select
tq.rule_no,
tq.posted_by_user_id,
tq.transaction_id,
tq.transaction_sr_no,
tq.dr_amount,
tq.cr_amount,
tq.tran_crncy_code,
tq.bkdt_tran_flg,
bank_desc_temp;
end;

When you say "not allowed" what do you mean? Did you get an error?
I ask, because subqueries are definitely allowed inside an insert as select statement, providing you have the syntax correct (and the subquery returns at most one row), e.g.:
create table test_tab (col1 number, col2 varchar2(10));
begin
insert into test_tab
select 1,
(select 'Yes' from dual d2 where d.dummy = d2.dummy)
from dual d;
commit;
end;
/
select * from test_tab;
COL1 COL2
---------- ----------
1 Yes
There are some syntax issues with the code you provided - where is the from clause, and where are the tq and gam aliases defined?

There are two syntax you can use in your insert statement:
(I)
INSERT INTO table_name( column1, column2....columnN)
VALUES ( value1, value2....valueN);
(II)
INSERT INTO table (column1, column2, ... )
SELECT expression1, expression2, ...
FROM source_table(s)
WHERE conditions;
In your example, you should choose the second approach:
insert into GL_DTPJ_TEST2 (rule_no,
posted_by_user_id,
transaction_id,
transaction_sr_no,
dr_amount,
cr_amount,
tran_crncy_code,
bkdt_tran_flg,
bank_desc
)
select tq.rule_no,
tq.posted_by_user_id,
tq.transaction_id,
tq.transaction_sr_no,
tq.dr_amount,
tq.cr_amount,
tq.tran_crncy_code,
tq.bkdt_tran_flg,
ent.bank_desc
from crmuser.gam
join crmuser.end ent
on ent.bank_id = gam.bank_id
;
basically, if you want to add records using an insert statement, you should use a full select statement first. Here is how I would do it:
(1)
select *
from table1;
(2)
select column1
,column2
,column3
from table1;
(3)
select t1.column1
,t1.column2
,t1.column3
,t2.column4
,t2.column5
from table1 t1
join table2 t2
on t2.id = t1.id
;
(4)
insert into table3 (col1
,col2
,col3
,col4
,col5)
select t1.column1
,t1.column2
,t1.column3
,t2.column4
,t2.column5
from table1 t1
join table2 t2
on t2.id = t1.id
;

Related

Oracle delete from tableA where a duplicate row is in tableB

As the title says, I am looking for a way to remove all rows from TableA where there is a matching row in TableB.
the Tables A & B have about 30 columns in them so a WHERE A.col1 = B.col1 etc would be a little problematical. Ideally I was hoping for something like
DELETE FROM tableA WHERE IN TableB
(overly simplified by this type of thing)
IN clause can compare all columns returned from select
DELETE FROM tableA WHERE ( col1,col2,col3,.. ) IN ( select col1,col2,col3... FROM TableB );
The brute force way to establish if two records from each table are the same is to just compare every column:
DELETE
FROM tableA a
WHERE EXISTS (SELECT 1 FROM tableB b WHERE a.col1 = b.col1 AND a.col2 = b.col2 AND ...
a.col30 = b.col30);
You could create function which checks structures of tables and, if they are the same, creates string containing correct conditions to compare.
For example here are two tables:
create table t1 (id, name, age) as (
select 1, 'Tom', 67 from dual union all
select 2, 'Tia', 42 from dual union all
select 3, 'Bob', 16 from dual );
create table t2 (id, name, age) as (
select 1, 'Tom', 51 from dual union all
select 3, 'Bob', 16 from dual );
Now use function:
select generate_condition('T1', 'T2') from dual;
result:
T1.ID = T2.ID and T1.NAME = T2.NAME and T1.AGE = T2.AGE
Copy this, paste and run delete query:
delete from t1 where exists (select 1 from t2 where <<PASTE_HERE>>)
Here is the function, adjust it if needed. I used user_tab_columns so if tables are on different schemas you need all_tab_columns and compare owners too. If you have Oracle 11g you can replace loop with listagg(). Second table has to contain all columns of first table and they have to be same type and length.
create or replace function generate_condition(i_t1 in varchar2, i_t2 in varchar2)
return varchar2 is
v varchar2(1000) := '';
begin
for rec in (select column_name, u2.column_id
from user_tab_cols u1
left join (select * from user_tab_cols where table_name = i_t2) u2
using (column_name, data_type, data_length)
where u1.table_name = i_t1 order by u1.column_id)
loop
if rec.column_id is null then
v := 'ERR: incompatible structures';
goto end_loop;
end if;
v := v||' and '||i_t1||'.'||rec.column_name
||' = '||i_t2||'.'||rec.column_name;
end loop;
<< end_loop >>
return(ltrim(v, ' and '));
end;
If you want to avoid running process manually you need dynamic PL/SQL.
create table tableA (a NUMBER, b VARCHAR2(5), c INTEGER);
create table tableB (a NUMBER, b VARCHAR2(5), c INTEGER);
As you said
WHERE A.col1 = B.col1 etc would be a little problematical
you could intersect the tables and mention all columns from tableA one time, like this:
delete tableA
where (a,b,c) in (select * from tableA
intersect
select * from tableB);

Query taking long when i use user defined function with order by in oracle select

I have a function, which will get greatest of three dates from the table.
create or replace FUNCTION fn_max_date_val(
pi_user_id IN number)
RETURN DATE
IS
l_modified_dt DATE;
l_mod1_dt DATE;
l_mod2_dt DATE;
ret_user_id DATE;
BEGIN
SELECT MAX(last_modified_dt)
INTO l_modified_dt
FROM table1
WHERE id = pi_user_id;
-- this table contains a million records
SELECT nvl(MAX(last_modified_ts),sysdate-90)
INTO l_mod1_dt
FROM table2
WHERE table2_id=pi_user_id;
-- this table contains clob data, 800 000 records, the table 3 does not have user_id and has to fetched from table 2, as shown below
SELECT nvl(MAX(last_modified_dt),sysdate-90)
INTO l_mod2_dt
FROM table3
WHERE table2_id IN
(SELECT id FROM table2 WHERE table2_id=pi_user_id
);
execute immediate 'select greatest('''||l_modified_dt||''','''||l_mod1_dt||''','''||l_mod2_dt||''') from dual' into ret_user_id;
RETURN ret_user_id;
EXCEPTION
WHEN OTHERS THEN
return SYSDATE;
END;
this function works perfectly fine and executes within a second.
-- random user_id , just to test the functionality
SELECT fn_max_date_val(100) as max_date FROM DUAL
MAX_DATE
--------
27-02-14
For reference purpose i have used the table name as table1,table2 and table3 but my business case is similar to what i stated below.
I need to get the details of the table1 along with the highest modified date among the three tables.
I did something like this.
SELECT a.id,a.name,a.value,fn_max_date_val(id) as max_date
FROM table1 a where status_id ='Active';
The above query execute perfectly fine and got result in millisecods. But the problem came when i tried to use order by.
SELECT a.id,a.name,a.value,a.status_id,last_modified_dt,fn_max_date_val(id) as max_date
FROM table1 where status_id ='Active' a
order by status_id desc,last_modified_dt desc ;
-- It took almost 300 seconds to complete
I tried using index also all the values of the status_id and last_modified, but no luck. Can this be done in a right way?
How about if your query is like this?
select a.*, fn_max_date_val(id) as max_date
from
(SELECT a.id,a.name,a.value,a.status_id,last_modified_dt
FROM table1 where status_id ='Active' a
order by status_id desc,last_modified_dt desc) a;
What if you don't use the function and do something like this:
SELECT a.id,a.name,a.value,a.status_id,last_modified_dt x.max_date
FROM table1 a
(
select max(max_date) as max_date
from (
SELECT MAX(last_modified_dt) as max_date
FROM table1 t1
WHERE t1.id = a.id
union
SELECT nvl(MAX(last_modified_ts),sysdate-90) as max_date
FROM table2 t2
WHERE t2.table2_id=a.id
...
) y
) x
where a.status_id ='Active'
order by status_id desc,last_modified_dt desc;
Syntax might contain errors, but something like that + the third table in the derived table too.

Insert into select query with cursor value

I want to write a SQK script with insert query in Oracle where one of the value will be fetched from cursor and rest all will be retrieved from table.
For example, consider Employee table:
Emp_No | Emp_Name
1 | AAA
...........
I am reading the table into a cursor.
Cursor c1 is select emp_no, emp_name from employee;
I am iterating the cursor and adding to a table along with information from another table.
for empCur in c1
loop
insert into employee_info(emp_no, emp_name, address, age, ... ) values (empCur.emp_no, empCur.emp_name, select t.address, t.age, ... from employee_temp_table t where t.emp_no=empCur.emp_no)
end loop;
Is my script valid? If not is there any other way to achieve it? Since few values are in cursor and few are in another table I am not sure how to handle this.
Your script isn't correct because this
select t.address, t.age, ... from employee_temp_table t where t.emp_no=empCur.emp_no
is not a valid expression. You may use a scalar subquery (one-row, one-column subquery) only, like this:
insert into t1(col1, col2, col3) values (1, (select col1 from t2), (select col2 from t2));
Or you may try insert from select:
for empCur in c1 loop
insert into employee_info(emp_no, emp_name, address, age, ... )
select empCur.emp_no, empCur.emp_name, t.address, t.age, ... from employee_temp_table t where t.emp_no=empCur.emp_no;
end loop;
If you want to use the cursor, why not just join the tables inside the cursor?
for empCur in ( select e.emp_no, e.emp_name, t.address, t.age ...
from employee e join employee_temp_table t on ( t.emp_no = e.emp_no )
) loop
insert into employee_info(...) values ( empCur.emp_no, ...);
end loop;
Or with a sql insert: (if you can choose sql over pl/sql - T Kyte says do it)
insert into employee_info
select e.emp_no, e.emp_name, t.address, t.age ...
from employee e join employee_temp_table t on ( t.emp_no = e.emp_no );

Oracle Equivalent to MySQL INSERT IGNORE?

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');

PL/SQL how do I select into table type local variable

PROCEDURE "ARCHIVE_CASE_LIST"
(
a_case_id_list IN INLISTNUMBERS
)
IS
l_customers INLISTNUMBERS;
INLISTNUMBERS is Oracle table of numbers;
How do I prepare pre-calculated list of customers and store them in l_customers, so that I don't need to use that long select statement in other update/select statements?
insert into table(l_customers) <-- fail
select distinct case1.customer_id into l_customers from case case1
where case1.case_id in (select column_value from table(a_case_id_list)) and
not exists (select 0 from case case2 where case2.customer_id = case1.customer_id and
case2.lifecycle_code not in (code_id('LIFECYCLE','A'), code_id('LIFECYCLE','D')));
update customer set customer.lifecycle_code = code_id('LIFECYCLE','A')
where customer.customer_id in (select column_value from table(l_customers));
open l_persons for
select person_id from person where person.customer_id in
(select column_value from table(l_customers));
Use a SELECT statement with BULK COLLECT:
select distinct case1.customer_id bulk collect into l_customers
from case case1
where case1.case_id in (select column_value from table(a_case_id_list)) and
not exists (select 0 from case case2 where case2.customer_id = case1.customer_id and
case2.lifecycle_code not in (code_id('LIFECYCLE','A'), code_id('LIFECYCLE','D')));

Resources