I am trying to create a procedure that accepts two table names as parameters, and then copies the rows from one table into another, one at a time. I know bulk insert and SELECT INTO are better ways to do this, but bulk insert isn't working because the table triggers throw mutation errors when I insert more than one row at once.
I've seen other answers that recommend using dynamic SQL, but I'm stuck on how to define the cursor that way.
CREATE OR REPLACE PROCEDURE TABLE_INSERT(
donor_t IN VARCHAR2,
empty_t IN VARCHAR2
)
AS
CURSOR C1 IS
SELECT * FROM donor_t;
BEGIN
FOR row IN C1
LOOP
INSERT INTO empty_t VALUES row;
END LOOP;
END;
When compiled as written above, compiler throws ORA-00942: table or view does not exist. When compiled with table names hard-coded in, this function inserts the rows as expected, without errors.
try this instead:
CREATE OR REPLACE PROCEDURE TABLE_INSERT(
donor_t IN VARCHAR2,
empty_t IN VARCHAR2
)
AS
V_SQL VARCHAR2(1000);
BEGIN
V_SQL := 'INSERT INTO ' || empty_t || ' SELECT * FROM ' || donor_t;
EXECUTE IMMEDIATE V_SQL;
END;
/
Related
Hi i'm working on this query in oracle and i need to provide many id to a procedure from a table. how can i provide each id from a table to my procedure. sory i'm kinda new at this im completely lost i dont know what to search.
here's
Procedure
PROCEDURE procedname(in_id in VARCHAR2)
select id from mytable
Here's what i tryed
execute procedname(select id from mytable);
but did no work
Is there a way to achive this?
Hope somone help me out with this
You can pass a collection of numbers. Here is an example on how to pass.
--sys.odcinumberlist is a collection which can hold numbers..
create procedure sp_test(i_id in sys.odcinumberlist)
as
l_cnt int;
begin
select count(*)
into l_cnt
from TABLE(i_id); /* the TABLE keyword is used to unfold the collection of numbers as rows..*/
dbms_output.put_line(l_cnt);
end;
/
--calling the stored procedure
begin
dbms_output.enable;
sp_test(sys.odcinumberlist(1,2,3,4,5,6)); /* here i am passing a list of numbers from 1 to 6*/
--the procedure will count the number of elements in the input collection which is 6
end;
/
You cannot directly use a SQL statement as an argument for a procedure or function. Since that needs an INTO clause in order to return the content of the SELECT statement. Your case suggests a CURSOR as needs to return all the records at a time. For this, a possible sample solution using SYS_REFCURSOR as an IN/OUT(or just OUT) type of parameter would be ;
SQL> CREATE TABLE mytable( id VARCHAR2(1) );
SQL> INSERT INTO mytable VALUES('A');
SQL> INSERT INTO mytable VALUES('B');
SQL> CREATE OR REPLACE PROCEDURE Convert_ID(p_myrecordset IN OUT SYS_REFCURSOR) AS
BEGIN
OPEN p_myrecordset FOR
SELECT id, ASCII( id )
FROM mytable
ORDER BY id;
END;
/
SQL> SET SERVEROUTPUT ON;
SQL> DECLARE
l_cursor SYS_REFCURSOR;
l_value1 mytable.id%TYPE;
l_value2 INT;
BEGIN
Convert_ID(p_myrecordset => l_cursor);
LOOP
FETCH l_cursor
INTO l_value1, l_value2;
EXIT WHEN l_cursor%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(l_value1 || ' - ' || l_value2 );
END LOOP;
CLOSE l_cursor;
END;
/
A - 65
B - 66
Demo
To take each id from some table and call sometable(id), a PL/SQL loop would be something like this:
begin
for r in (
select id from sometable
)
loop
procedname(r.id);
end loop;
end;
I am working on a procedure to transpose data from a large matrix into a table consisting of three columns. I'm having some difficulty dynamically inserting rows into the table. When I try to execute the procedure block below, I get an error mesage:
ORA-00936: missing expression
ORA-06512: at line 24
00936. 00000 - "missing expression"
The procedure generates a valid insert statement, that I can copy and execute as static SQL. Everything up to execute immediate stmnt is working properly. Moreover, I have a nearly identical procedure that functions perfectly. There is only one difference between the two. In the working version, all of the values inserted are of type "VARCHAR2". I'm at a loss as to how to continue troubleshooting.
declare
type rec_type is record(
row_name varchar2(250),
measurement number(30,27)
);
my_rec rec_type;
type cols_type is table of varchar2(10);
cols cols_type;
stmnt varchar2(2000);
cur sys_refcursor;
begin
select colnames bulk collect into cols from p100_stg1_tmnt_meta;
for i in cols.first..cols.last loop
stmnt := 'select site_id, '|| cols(i) ||' from p100_stg1_site_matrix';
open cur for stmnt;
loop
fetch cur into my_rec;
exit when cur%notfound;
stmnt := 'insert into p100_stg1_site_measurement (site_id, col_name, measurement) values '||
'('''||my_rec.row_name ||''', '''||cols(i)||''', '||my_rec.measurement||')';
--dbms_output.put_line(stmnt);
execute immediate stmnt;
end loop;
end loop;
end;
/
An example of an insert statement generated by the above procedure:
insert into p100_stg1_site_measurement (
site_id,
col_name,
measurement
)
values (
'5715_C17orf85_S500_RPHS[+80]PEKAFSSNPVVR',
'tmnt_2',
.0288709682691077
)
Environment:
SQL Developer on Ubuntu 16.04
Oracle 12c Community Edition.
You should use bind variables, i.e.
stmnt := 'insert into p100_stg1_site_measurement (site_id, col_name, measurement)
values (:site_id, :col, :measurement)';
execute immediate stmnt using my_rec.row_name, cols(i), my_rec.measurement;
How do I write a PL / SQL program that creates and fills a table for example let's call the table "TABEMP1", it fills it with data from any other table.
What I have so far;
set serveroutput on
set verify off
DECLARE
Cursor cur_emp IS SELECT * FROM EMP;
v_emp EMP%ROWTYPE;
BEGIN
OPEN cur_emp;
LOOP
FETCH cur_emp INTO v_emp;
EXIT WHEN cur_emp%NOTFOUND;
dbms_output.put_line('Ename: ' || v_emp.ename || ' Empno: ' || v_emp.empno);
END LOOP;
CLOSE cur_emp;
END;
/
No need for a cursor, using Native SQL with EXECUTE IMMEDIATE you can achieve that with flexible features:
DECLARE
lv_src_table_name VARCHAR2(30) := 'EMP';
pi_new_table_name VARCHAR2(30) := 'emp_copy';
BEGIN
EXECUTE IMMEDIATE 'CREATE TABLE '||pi_new_table_name||' AS SELECT * FROM '||pi_src_table_name;
END;
/
You can turn that into procedure and pass the two parameters with any original/copy tables names you want
In this case you dont need a PLSQL to achieve. A single SQL statement will be more effective. Hope below snippet helps.
CREATE TABLE TEMP_EMP
AS
SELECT * FROM EMP;
I want to create a nested procedure where in the 1st procedure, I want to create a table dynamically with 2 columns and in the 2nd procedure, I want to insert values into that table.
Below is the code I am trying to use; what am I doing wrong?
CREATE or replace PROCEDURE mytable (tname varchar2)
is
stmt varchar2(1000);
begin
stmt := 'CREATE TABLE '||tname || '(sname varchar2(20) ,sage number (4))';
execute immediate stmt;
end;
create PROCEDURE mytable1 (emp_name varchar2,emp_age number,tname varchar2)
is
stmt1 varchar2(1000);
begin
stmt1 := 'insert into '||tname||' values ('Gaurav' ,27)';
execute immediate stmt1;
end;
There's no need to create a nested procedure here. You can do everything in a single procedure.
Note my use of bind variables in the execute immediate statement
create or replace procedure mytable (
Ptable_name in varchar2
, Pemp_name in varchar2
, Pemp_age in number
) is
begin
execute immediate 'create table ' || Ptable_name
|| ' (sname varchar2(20), sage number (4))';
execute immediate 'insert into ' || Ptable_name
|| ' values (:emp_name, :emp_age)'
using Pemp_name, Pemp_age;
end;
More generally, there's no need to use execute immediate at all; creating tables on the fly is indicative of a poorly designed database. If at all possible do not do this; create the table in advance and have a simple procedure to insert data, should you need it:
create or replace procedure mytable (
, Pemp_name in varchar2
, Pemp_age in number
) is
begin
insert into my_table
values (Pemp_name, Pemp_age);
end;
I would highly recommend reading Oracle's chapter on Guarding Against SQL Injection.
If you really feel like you have to do this as a nested procedure it would look like this; don't forget to call the nested procedure in the main one as the nested procedure isn't visible outside the scope of the first.
create or replace procedure mytable (
Ptable_name in varchar2
, Pemp_name in varchar2
, Pemp_age in number
) is
procedure myvalues (
Pemp_name in varchar2
, Pemp_age in number
) is
begin
execute immediate 'insert into ' || Ptable_name
|| ' values (:emp_name, :emp_age)'
using Pemp_name, Pemp_age;
end;
begin
execute immediate 'create table ' || Ptable_name
|| ' (sname varchar2(20), sage number (4))';
myvalues ( Pemp_name, Pemp_age);
end;
Please see Oracle's documentation on PL/SQL subprograms
I have one table called event, and created another global temp table tmp_event with the same columns and definition with event. Is it possible to insert records in event to tmp_event using this ?
DECLARE
v_record event%rowtype;
BEGIN
Insert into tmp_event values v_record;
END;
There are too many columns in event table, I want to try this because I don't want to list all the columns.
Forget to mention: I will use this in the trigger, can this v_record be the object :new after insert on EVENT table ?
To insert one row-
DECLARE
v_record event%rowtype;
BEGIN
SELECT * INTO v_record from event where rownum=1; --or whatever where clause
Insert into tmp_event values v_record;
END;
Or a more elaborate version to insert all rows from event-
DECLARE
TYPE t_bulk_collect_test_tab IS TABLE OF event%ROWTYPE;
l_tab t_bulk_collect_test_tab;
CURSOR c_data IS
SELECT *
FROM event;
BEGIN
OPEN c_data;
LOOP
FETCH c_data
BULK COLLECT INTO l_tab LIMIT 10000;
EXIT WHEN l_tab.count = 0;
-- Process contents of collection here.
Insert into tmp_event values v_record;
END LOOP;
CLOSE c_data;
END;
/
In a trigger, yes it is possible but its like the chicken or the egg. You have to initialize every field of the rowtype with the :new column values like-
v_record.col1 := :new.col1;
v_record.col2 := :new.col2;
v_record.col3 := :new.col3;
....
Apparently, the PLSQL examples above cannot be used in a trigger since it would throw a mutating trigger error. And there is no other way for you to get the entire row in the trigger other than accessing each column separately as I explain above, so if you do all this why not directly use :new.col in the INSERT into temp_event itself, will save you a lot of work.
Also since you say it's a lot of work to mention all the columns, (in Oracle 11gR2) here's a quick way of doing that by generating the INSERT statement and executing it dynamically (although not tested for performance).
CREATE OR REPLACE TRIGGER event_air --air stands for "after insert of row"
AFTER INSERT ON EVENT
FOR EACH ROW
L_query varchar2(2000); --size it appropriately
BEGIN
SELECT 'INSERT INTO tmp_event VALUES ('|| listagg (':new.'||column_name, ',')
WITHIN GROUP (ORDER BY column_name) ||')'
INTO l_query
FROM all_tab_columns
WHERE table_name='EVENT';
EXECUTE IMMEDIATE l_query;
EXCEPTION
WHEN OTHERS THEN
--Meaningful exception handling here
END;
There is a way to insert multiple rows into table with %Rowtype.
checkout below example.
DECLARE
TYPE v_test IS TABLE OF TEST_TAB%rowtype;
v_test_tab v_test ;
EXECUTE immediate ' SELECT * FROM TEST_TAB ' bulk collect INTO v_test_tab ;
dbms_output.put_line('v_test_tab.count -->'||v_test_tab.count);
FOR i IN 1..v_test_tab.count
LOOP
INSERT INTO TEST_TAB_1 VALUES v_test_tab
(i
) ;
END LOOP;
END;
sum up to full working excample ...
DECLARE
TYPE t_bulk_collect_test_tab IS TABLE OF event%ROWTYPE;
l_tab t_bulk_collect_test_tab;
CURSOR c_data IS SELECT * FROM event;
BEGIN
OPEN c_data;
LOOP
FETCH c_data
BULK COLLECT INTO l_tab LIMIT 10000;
EXIT WHEN l_tab.count = 0;
FORALL i IN 1..l_tab.count
Insert into tmp_event values l_tab(i);
commit;
END LOOP;
CLOSE c_data;
END;
/