Insert into another table after manipulations - oracle

I have two tables with same structures in two different databases.
Now, I want to copy the contents from one table (say table1 of db1) into another table (say table2 of db2), but I want to perform certain manipulations of some of the columns before inserting.
For example if table1 of db1 has 50 columns, I want to manipulate some (say 5) columns and insert the rest as is
Following is what I have done.
I am writing a pl/sql script declaring as many variables as the table columns
then a cursor to select from table
and then iterating in a loop to perform manipulations on individual columns and inserting into table2
DECLARE
var1 datatype;
var2 datatype;
..
CURSOR MY_CUR is
SELECT * FROM table1;
BEGIN
FOR CUR_ROW in MY_CUR
LOOP
var1 := CUR_ROW.column1
var2 := CUR_ROW.column2
..
logic to modify some variables e.g. var1 := LPAD(var1,5,'1');
..
insert into table2#db2
(column1, column2 ..)
values
(var1, var2 ..);
END LOOP;
END;
This is very slow, is there any other efficient way to accomplish this?(perhaps BULK COLLECT or FORALL?) but using BULK COLLECT or FORALL, how could I perform manipulations on individual columns?
I am using Oracle10g

try to do one more thing build the collection using for loop which you are doing then insert it with bulk insert statement. first declare collection with row type of table which is used for insert. eg.
type table2_typ is table of table2#db2%rowtype
v_table2_typ table2_typ;
fill collection v_table2_typ in you basic for loop and then insert this using bulk collect insert eg.
FORALL i IN 1..v_table2_typ.COUNT
INSERT INTO t1 VALUES v_table2_typ(i);
you need to use limit clause and commit mechanism if your data volume is more.

Related

how can I dynamically select a column from a list of tables and output timestamp results?

I have a large number of identical tables containing columns "eventtime" and "eventvalue"
eventtime is TIMESTAMP(6) and eventvalue is NUMBER (but I don't care about it.)
table names are like data_001, data_002, data_003, etc. all identically defined but it doesn't matter because I have another table called table_list which contains a list of these tables by name. (They are dynamically generated.)
So "select distinct data_table from table_list" gets you output like
data_001
data_002
data_003
and so on.
I am trying to extract the minimum timestamp from the eventtime column of each table and output it to DBMS_OUTPUT and I can't get it to work.
DECLARE
mystr VARCHAR(1000);
v_mystamp TIMESTAMP(6);
cursor c1 is
select distinct data_table from table_list order by data_table asc;
BEGIN
FOR rec IN c1 LOOP
mystr := 'select min(eventtime) into :v_mystamp from ' || rec.data_table;
execute immediate mystr;
DBMS_OUTPUT.PUT_LINE(rec.data_table);
DBMS_OUTPUT.PUT_LINE(v_mystamp);
END LOOP;
END;
The expected output is a list of tables, and the min(eventtime) from each table.
What I am getting is a list of tables, and then a blank line after each, and I am wondering what I am doing wrong... somehow it is not capturing the min(eventtime) properly (or not outputting it properly?) but I am not sure why.
Put INTO clause into execute immediate:
mystr := 'select min(eventtime) from ' || rec.data_table;
execute immediate mystr into v_mystamp ;

Create insert record dynamically by changing pk of existing record for passed in table

I want to pass a table name and schema into a procedure, and have it generate insert, update and delete statements for the particular table. This is part of an automated testing solution (in a development environment) in which I need to test some change data capture. I want to make this dynamic as it is going to be need to be done for lots of different tables over a long period of time, and I need to call it via a REST request through ORDS, so don't want to have to make an endpoint for every table.
Update and delete are fairly easy, however I am struggling with the insert statement. Some of the tables being passed in have hundreds of columns with various constraints, fks etc. so I think it makes sense to just manipulate an existing record by changing only the primary key. I need to be able to modify the primary key to a new value known to me beforehand (e.g. '-1').
Ideally I would create a dynamic rowtype, and select into where rownum = 1, then loop round the primary keys found from all_constraints, and update the rowtype.pk with my new value, before inserting this into the table. Essentially the same as this but without knowing the table in advance.
e.g. rough idea
PROCEDURE manipulate_records(p_owner in varchar2, p_table in varchar2)
IS
cursor c_pk is
select column_name
from all_cons_columns
where owner = p_owner
and constraint_name in (select constraint_name
from all_constraints
where table_name = p_table
and constraint_type = 'P');
l_row tbl_passed_in%ROWTYPE --(I know this isn't possible but ideally)
BEGIN
-- dynamic sql or refcursor to collect a record
select * into tbl_passed_in from tablename where rownum = 1;
-- now loop through pks and reassign their values to my known value
for i in c_pk loop
...if matches then reassign;
...
end loop;
-- now insert the record into the table passed in
END manipulate_records;
I have searched around but haven't found any examples which fit this exact use case, where an unknown column needs to be modified and insert into a table.
Depending on how complex your procedure is, you might be able to store it as a template in a CLOB. Then pull it in, replace table and owner, then compile it.
DECLARE
prc_Template VARCHAR2(4000);
vc_Owner VARCHAR2(0008);
vc_Table VARCHAR2(0008);
BEGIN
vc_Table := 'DUAL';
vc_Owner := 'SYS';
-- Pull code into prc_Template from CLOB, but this demonstrates the concept
prc_Template := 'CREATE OR REPLACE PROCEDURE xyz AS r_Dual <Owner>.<Table>%ROWTYPE; BEGIN NULL; END;';
prc_Template := REPLACE(prc_Template,'<Owner>',vc_Owner);
prc_Template := REPLACE(prc_Template,'<Table>',vc_Table);
-- Create the procedure
EXECUTE IMMEDIATE prc_Template;
END;
Then you have the appropriate ROWTYPE available:
CREATE OR REPLACE PROCEDURE xyz AS r_Dual SYS.DUAL%ROWTYPE; BEGIN NULL; END;
But you can't create the procedure and run it in the same code block.

Is it possible to return the Primary Key on an Insert as select statement - Oracle?

So I usually get the Primary Key of a newly inserted record as the following while using a trigger.
insert into table1 (pk1, notes) values (null, "Tester") returning pk1
into v_item;
I am trying to use the same concept but with an insert using a select statement. So for example:
insert into table1 (pk1, notes) select null, description from table2 where pk2 = 2 returning pk1
into v_item;
Note:
1. There is a trigger on table1 which automatically creates a pk1 on insert.
2. I need to use a select insert because of the size of the table that is being inserted into.
3. The insert is basically a copy of the record, so there is only 1 record being inserted at a time.
Let me know if I can provide more information.
I don't believe you can do this with insert/select directly. However, you can do it with PL/SQL and FORALL. Given the constraint about the table size, you'll have to balance memory usage with performance using l_limit. Here's an example...
Given this table with 100 rows:
create table t (
c number generated by default as identity,
c2 number
);
insert into t (c2)
select rownum
from dual
connect by rownum <= 100;
You can do this:
declare
cursor t_cur
is
select c2
from t;
type t_ntt is table of number;
l_c2_vals_in t_ntt;
l_c_vals_out t_ntt;
l_limit number := 10;
begin
open t_cur;
loop
fetch t_cur bulk collect into l_c2_vals_in limit l_limit;
forall i in indices of l_c2_vals_in
insert into t (c2) values (l_c2_vals_in(i))
returning c bulk collect into l_c_vals_out;
-- You have access to the new ids here
dbms_output.put_line(l_c_vals_out.count);
exit when l_c2_vals_in.count < l_limit;
end loop;
close t_cur;
end;
You can't use that mechanism; as shown in the documentation railroad diagram:
the returning clause is only allowed with the values version, not with the subquery version.
I'm interpreting your second restriction (about 'table size') as being about the number of columns you would have to handle, possibly as individual variables, rather than about the number of rows - I don't see how that would be relevant here. There are ways to avoid having lots of per-column local variables though; you could select into a row-type variable first:
declare
v_item number;
v_row table1%rowtype;
begin
...
select null, description
into v_row
from table2 where pk2 = 2;
insert into table1 values v_row returning pk1 into v_item;
dbms_output.put_line(v_item);
...
or with a loop, which might make things look more complicated than necessary if you really only ever have a single row:
declare
v_item number;
begin
...
for r in (
select description
from table2 where pk2 = 2
)
loop
insert into table1 (notes) values (r.description) returning pk1 into v_item;
dbms_output.put_line(v_item);
...
end loop;
...
or with a collection... as #Dan has posted while I was answering this so I won't repeat! - though again that might be overkill or overly complicated for a single row.

PL/SQL Extract Column Names and use in select statment

not sure if this is possible at all but im trying to do this with as little manual work as possible.
I have a table with 150 columns based on different combinations of factors.
I wish to extract the column names where a certain certain string is inside the column name.
I have done the following which does this. This is a basic example of what I have
--Create the table
Create Table temp
(id number,
Fac1_Fac2_Fac_3_Fac4_Fac5 number,
Fac1_Fac6_Fac_3_Fac4_Fac5 number,
Fac1_Fac6_Fac_7_Fac4_Fac5 number,
Fac1_Fac9_Fac_3_Fac4_Fac5 number,
Fac1_Fac10_Fac_3_Fac4_Fac5 number,
Fac1_Fac2_Fac_3_Fac11_Fac5 number,
Fac1_Fac2_Fac_3_Fac4_Fac12 number,
Fac13_Fac2_Fac_3_Fac4_Fac5 number);
Insert into temp Values (1,35634,3243,343,564,56,4635,3,334);
Insert into temp Values (2,3434234,3243,343,564,56,435,3,34234);
Insert into temp Values (3,5555,3243,33,564,56,435,3,3434);
Insert into temp Values (4,34234,343,343,564,56,4335,3,34);
commit;
--Extract Column Names
Select * from (
Select COLUMN_NAME
from user_tab_cols
where lower(table_name) ='temp'
)
where column_name like '%FAC13%'
--This is what I want to automate.
Select id, FAC13_FAC2_FAC_3_FAC4_FAC5
From temp
--I want the column name to come fron the select statment above as there may be lots of names.
Basically, I want to select all the rows from my table that have Fac13 in the column name all in one query if possible.
Thanks
I do not think you can do that in one query. First, your extract column names query can be simplified to one query as a cursor, and then use a dynamic select statement as follows:
CREATE OR REPLACE proc_dyn_select IS
CURSOR c1 IS
SELECT column_name
FROM user_tab_cols
WHERE LOWER(table_name) ='temp' and column_name LIKE '%FAC13%';
cols c1%ROWTYPE;
sqlstmt VARCHAR2(2000);
BEGIN
OPEN c1;
LOOP
FETCH c1 into cols;
EXIT WHEN c1%NOTFOUND;
sqlstmt := sqlstmt ||cols.column_name||',';
END LOOP;
CLOSE c1;
sqlstmt := 'select '||substr(sqlstmt, 1, length(sqlstmt)-1)||' FROM temp';
EXECUTE IMMEDIATE sqlstmt;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('error '||sqlerrm);
END;
/
Explanation
First, the cursor will store the columns that meet your conditions (to be from the table temp and the column names have the sub string FAC13. Then in execution section (after BEGIN), you will build your query dynamically using columns names stored in the cursor c1. With each round of the loop, a column name is added as a string and concatenated with a comma. So a string of columns will be built like this 'col1, col2, col3, ... coln,'. The string is stored in sqlstmt variable.
After the loop end, you amend the string to build sql statement, by adding the keywords SELECT, FROM and table name. However, we remove the last character of the sqlstmt variable, as it is an extra comma.
EXECUTE IMMEDIATE statement, will run the query stored in sqlstmt.
By using a procedure, you can always pass parameters, such that this procedure can perform any dynamic sql statement you want.

table column containing list of other table column

create table cdi(comp_id varchar2(3),pk_key varchar2(2000));
insert into cdi values('abc','empno,ename,job');
insert into cdi values('pqr','empno,job,mgr');
insert into cdi values('cde','empno,mgr,sal');
commit;
create table emp_test(empno integer,ename varchar2(200),job varchar2(200),mrg integer,sal integer);
insert into emp_test values(1,'Gaurav Soni','DB',12,12000);
insert into emp_test values(2,'Niharika Saraf','Law',13,12000);
insert into emp_test values(2,'Niharika Saraf','Law',13,12000);
insert into emp_test values(3,'Saurabh Soni',null,12,12000);
commit;
In cdi table comp_id is primary key
create or replace procedure test(p_comp_id IN cdi.comp_id%TYPE
,p_empno IN emp_test.empno%TYPE
)
IS
TYPE ref_cur is ref cursor;
v_cur ref_cur;
v_pk_key cdi.pk_key%TYPE;
BEGIN
OPEN v_cur is select pk_key from cdi where comp_id =p_comp_id;
fetch v_cur into v_pk_key;
--now this list v_pk_key is primary key for that comp_id
--so following things need to be done
--1.check the emp_test table with this column list (v_pk_key )
--2. whether for that emp_no the primary key is null eg.
-- incase of comp_id cde ...empno,mgr,sal value should be not null
--if any of the value is null raise an error
--3.If there are two rows for that primary also raise an error.
-- for eg comp_id=abc two rows are fetched from emp_test
close v_cur;
END;
I am not sure of the approach what should i do to,first i think of concatenating the v_pk_key like empno||ename||job and then used this in select query ,but not able to check for null values ,i am confused what to do .
EDIT
what i have tried was to convert the list v_pk_key to
NVL(empno,'$')||NVL(ename,'$')||NVL(job,'$') and then
select v_pk_list from emp_test where empno=p_empno;
and then check for $ in the result if there is no $ in the result i ll check for more than one row,but i am not finding this as an efficient solution
if anyone give me a jist of it ,i will solve this .
I would split out that list of values, which really represents 3 columns ('empno, ename, job'). Use instr function, or create a separate function to split and return a pl/sql table, but either way it would be much more clear what is intended in the code.
See here for a SO link to some examples using instr to split csv fields.
Once you have 3 separate local variables with these values (l_empno, l_ename, l_job), then you can use much easier in your various SQL statements (where l_empno = blah and l_ename not in (blahblah)), etc...

Resources