Write with cursor into a table (table name as parameter) - sql

How could I insert in a table using CURSOR with a name gives as a parameter?
Thanks
PROCEDURE delta (pTableName IN VARCHAR2, pStichTag IN DATE) IS
lTabName := VARCHAR(30);
v_stmt_str := VARCHAR(4000);
cCursor SYS_REFCURSOR;
BEGIN
lTabName := substr(pTableName,5);
v_stmt_str := 'SELECT * FROM '|| lTableName ||' WHERE dwh_date =
to_date('||pStichTag||','DD.MM.YY');
OPEN cCursor FOR v_stmt_str USING 'MANAGER';
LOOP
FETCH cCursor INTO pTableNAME%ROWTYPE;
-- UPATE pTableName SET some WHERE this line
END LOOP;
CLOSE cCursor;

You need to use EXECUTE IMMEDIATE to perform the update:
EXECUTE IMMEDIATE 'UPDATE ' || pTableName ||
' SET SOME_COLUMN = 12345 WHERE SOME_OTHER_COLUMN = ''xyz''';
However, you're going to have to re-do your cursor logic. Because you don't know the table name you don't know which fields will be fetched prior to opening the cursor, so you're going to have to hard-code the field names to be fetched instead of using *. If that's not to your liking you'll have to use the DBMS_SQL package, which allows for more flexibility - but I'll warn you, it's rather complex to use.

Corrected code snippet
PROCEDURE delta (pTableName IN VARCHAR2, pStichTag IN DATE)
IS
lTabName VARCHAR(30); -- removed :=
v_stmt_str VARCHAR(4000); -- removed :=
cCursor SYS_REFCURSOR;
BEGIN
lTabName := substr(pTableName,5); -- I dont understand why only first five digits of table name is used. May be your logic
v_stmt_str := 'SELECT * FROM '|| lTableName ||' WHERE dwh_date =
to_date('''||pStichTag||''',''DD.MM.YY'')'; -- added few ' for proper string concatenation
OPEN cCursor FOR v_stmt_str USING MANAGER; -- removed ' from MANAGER, MANAGER must be some variable and for your cusrsor, there is no bind variable so there is no need of USING clause at all
-- You can use just: OPEN cCursor FOR v_stmt_str;
LOOP
FETCH cCursor INTO pTableNAME%ROWTYPE;
-- UPATE pTableName SET some WHERE this line
END LOOP;
CLOSE cCursor;
I hope this will be useful.
Cheers!!

Related

how to check if SYS_REFCURSOR is empty

I have a general SYS_REFCURSOR that I want to check if it is empty or not.
The code is like this:
declare
v_cursor SYS_REFCURSOR;
begin
OPEN v_cursor FOR <any select statement>.
check if v_cursor is empty.
end;
Can someone tell me how to check if the weak cursor is empty, please?
I have to mention that the base SELECT statement can be anything from any table.
The column numbers or type it is known only at runtime.
Thank you,
You can't see if a ref cursor contains data without fetching, and consuming at least one row from it.
If you really need to determine this at the point the cursor is opened, without knowing the structure at that point, you could execute a modified query that just counts the rows returned by your real query - possibly limited to a single row if that helps performance - either as a simple execute immediate ... into ... or with a separate open/fetch/close for consistency; something like:
declare
v_cursor SYS_REFCURSOR;
v_query VARCHAR2(4000);
v_count PLS_INTEGER;
begin
v_query := <any select statement>;
-- see if the query finds any data
OPEN v_cursor FOR 'select count(*) from (' || v_query || ')'; -- could limit rows
FETCH v_cursor INTO v_count;
CLOSE v_cursor;
if v_count = 0 then
dbms_output.put_line('No data');
return;
end if;
dbms_output.put_line('Found data, opening cursor for real');
OPEN v_cursor FOR v_query;
-- loop over results, return to caller, etc.
end;
/
db<>fiddle
Try to fetch a row and then use v_cursor%NOTFOUND to determine of the cursor is empty:
DECLARE
v_cursor SYS_REFCURSOR;
v_value DUAL.DUMMY%TYPE;
BEGIN
OPEN v_cursor FOR SELECT DUMMY FROM DUAL WHERE 1 = 0;
FETCH v_cursor INTO v_value;
IF v_cursor%NOTFOUND THEN
DBMS_OUTPUT.PUT_LINE('Cursor empty');
ELSE
DBMS_OUTPUT.PUT_LINE('Cursor not empty');
END IF;
END;
/
or
DECLARE
CURSOR v_cursor IS
SELECT DUMMY FROM DUAL WHERE 1 = 0;
v_row v_cursor%ROWTYPE;
BEGIN
OPEN v_cursor;
FETCH v_cursor INTO v_row;
IF v_cursor%NOTFOUND THEN
DBMS_OUTPUT.PUT_LINE('Cursor empty');
ELSE
DBMS_OUTPUT.PUT_LINE('Cursor not empty');
END IF;
END;
/
Both output:
Cursor empty
db<>fiddle here

plsql: execute query: invalid identifier

Why can't I execute the stmt query?
It shows tabb is invalid identifier.
TYPE tab_row IS TABLE OF tab_name%ROWTYPE;
tabb tab_row;
TYPE cur_ref Is Ref Cursor;
c cur_ref;
stmt_string Varchar2(1000);
stmt Varchar2(1000);
Begin
stmt_string := 'Select * from ' || tab1 || ' mft
Where mft.mfmt_id IS NOT NULL
and mft.MFMT_FLAG IS NULL';
Open c For stmt_string;
Fetch c bulk collect into tabb;
Close c;
stmt:= 'Update '|| tab || ' m
Set m.mfmt_mat_bnr = tabb(i).mfmt_mat_bnr,
m.mfmt_mat_type = tabb(i).mfmt_mat_type,
m.mfmt_be_seg = tabb(i).mfmt_be_seg
Where m.mfmt_id = tabb(i).mfmt_id';
For i in 1..tabb.count loop
Execute Immediate stmt;
End loop;
End;
I have not seen where is tabb being declared. As you mentioned in your comment, it is a collection.
From your code I can understand you are looping through the values of this collection. This means these values are variables i.e. changing values every iteration. Therefore, you need to use them as such.
You current dynamic statement, this values are static string. This Set m.mfmt_mat_bnr = tabb(i).mfmt_mat_bnr, will result in the same string every iteration. Hence, you need to change that to Set m.mfmt_mat_bnr = '|| tabb(i).mfmt_mat_bnr||', ...etc
I can rewrite your code as follows:
TYPE tab_row IS TABLE OF tab_name%ROWTYPE;
tabb tab_row;
TYPE cur_ref Is Ref Cursor;
c cur_ref;
stmt_string Varchar2(1000);
stmt Varchar2(1000);
Begin
stmt_string := 'Select * from ' || tab1 || ' mft
Where mft.mfmt_id IS NOT NULL
and mft.MFMT_FLAG IS NULL';
Open c For stmt_string;
Fetch c bulk collect into tabb;
Close c;
stmt:= 'Update '|| tab || ' m
Set m.mfmt_mat_bnr = '||tabb(i).mfmt_mat_bnr||',
m.mfmt_mat_type = '||tabb(i).mfmt_mat_type||',
m.mfmt_be_seg = '||tabb(i).mfmt_be_seg||'
Where m.mfmt_id = '||tabb(i).mfmt_id;
For i in 1..tabb.count loop
Execute Immediate stmt;
End loop;
End;
Since this code is not tested, one thing I strongly recommend to confirm first. Is that tabb is correctly declared within the scope.
If the problem is in your loop where you execute your statement, maybe try using EXECUTE IMMEDIATE .. USING statement?
The syntax is:
EXECUTE IMMEDIATE <SQL Command>
[INTO <variable list>]
[USING <bind variable list>];
You might try the code below, then:
DECLARE
-- your tab1 may contain some insignificant columns with values (especially huge ones e.g. CLOB/BLOB)
-- which will extremely affect procedure's efficiency, so it's a better practice to select only those values that we need
TYPE t_tab_rec IS RECORD (
mfmt_mat_bnr tab_name.mfmt_mat_bnr%TYPE
,mfmt_mat_type tab_name.mfmt_mat_type%TYPE
,mfmt_be_seg tab_name.mfmt_be_seg%TYPE
,mfmt_id tab_name.mfmt_id%TYPE
);
TYPE t_tab_arr IS TABLE OF t_tab_rec;
tabb t_tab_arr := NEW t_tab_arr();
TYPE ref_cur IS REF CURSOR RETURN t_tab_rec; -- to assure cursor is returning t_tab_rec type records
c cur_ref;
stmt_select VARCHAR2(1000);
stmt_upadte VARCHAR2(1000);
BEGIN
stmt_select :=
'SELECT
mft.mfmt_mat_bnr
,mft.mfmt_mat_type
,mft.mfmt_be_seg
,mft.mfmt_id
FROM
'||tab1||' mft
WHERE
mft.mfmt_id IS NOT NULL
AND mft.mfmt_flag IS NOT NULL';
OPEN c FOR stmt_select;
FETCH c BULK COLLECT INTO tabb;
CLOSE c;
stmt_upadte :=
'UPDATE '||tab||'
SET mfmt_mat_bnr = :p_mfmt_mat_bnr
,mfmt_mat_type = :p_mfmt_mat_type
,mfmt_be_seg = :p_mfmt_be_seg
WHERE
mfmt_id = :p_mfmt_id';
FOR idx IN tabb.FIRST .. tabb.LAST
LOOP
EXECUTE IMMEDIATE stmt_update USING tabb(idx).mfmt_mat_bnr, tabb(idx).mfmt_mat_type, tabb(idx).mfmt_be_seg, tabb(idx).mfmt_id;
END LOOP;
END;
/

Dynamic SQL LOOP

Dynamic SQL is not my friend, basically the idea is that I can use the procedure with the "p_in_table" paramter to get the number of rows contained in the table.
CREATE OR REPLACE PROCEDURE how_many_rows(p_in_table VARCHAR2)
IS
TYPE cur_cur IS REF CURSOR;
v_cur_cur cur_cur;
v_rowcount NUMBER(28);
v_cur_txt VARCHAR2(299);
BEGIN
v_cur_txt := 'SELECT * FROM ' || p_in_table;
OPEN v_cur_cur FOR v_cur_txt;
LOOP
v_rowcount := v_cur_cur%ROWCOUNT;
EXIT WHEN v_cur_cur%NOTFOUND;
END LOOP;
CLOSE v_cur_cur;
dbms_output.put_line(v_rowcount);
END;
Would preciate it if someone would tell me what am I doing wrong?
The problem is that you not iterating through cursor - no fetch statement or something like that, so, basically, you have an infinite loop. To avoid this you need to do something like this:
CREATE OR REPLACE PROCEDURE how_many_rows
(p_in_table VARCHAR2) IS
TYPE cur_cur IS REF CURSOR;
v_cur_cur cur_cur;
v_rowcount NUMBER(28);
v_cur_txt VARCHAR2(299);
v_row SOME_TABLE%ROWTYPE; --add row variable
BEGIN
v_cur_txt := 'SELECT * FROM '|| p_in_table;
OPEN v_cur_cur FOR v_cur_txt;
LOOP
v_rowcount := v_cur_cur%ROWCOUNT;
FETCH v_cur_cur INTO v_row; --fetch a row in it
EXIT WHEN v_cur_cur%NOTFOUND;
END LOOP;
CLOSE v_cur_cur;
DBMS_OUTPUT.PUT_LINE(v_rowcount);
END;
But, as you can see, to do this you need to know, what table you're quering, so this is not general solution. Maybe there is a workaround for this, but i suggest, you use more simple and efficient approach, for example with EXECUTE IMMEDIATE:
CREATE OR REPLACE PROCEDURE HOW_MANY_ROWS(p_in_table VARCHAR2)
IS
v_tmp NUMBER;
BEGIN
EXECUTE IMMEDIATE 'SELECT COUNT(1) FROM ' || p_in_table INTO v_tmp;
DBMS_OUTPUT.PUT_LINE(v_tmp);
END;
Ok, I gave a thought on how to achieve this using your way, and here is what i've ended up with - just fetch ROWNUM from your table, every table has it and you know it's type - NUMBER. So this procedure will work in general case:
CREATE OR REPLACE PROCEDURE how_many_rows
(p_in_table VARCHAR2) IS
TYPE cur_cur IS REF CURSOR;
v_cur_cur cur_cur;
v_rowcount NUMBER(28);
v_cur_txt VARCHAR2(299);
v_row NUMBER; --add rownum variable
BEGIN
v_cur_txt := 'SELECT ROWNUM FROM '|| p_in_table; --select only rownum from target table
OPEN v_cur_cur FOR v_cur_txt;
LOOP
v_rowcount := v_cur_cur%ROWCOUNT;
FETCH v_cur_cur INTO v_row; --fetch rownum in it
EXIT WHEN v_cur_cur%NOTFOUND;
END LOOP;
CLOSE v_cur_cur;
DBMS_OUTPUT.PUT_LINE(v_rowcount);
END;

can TYPE be declared of ref cursor rowtype

TYPE ref_cur IS REF CURSOR;
ref_cur_name ref_cur;
TYPE tmptbl IS TABLE OF ref_cur_name%ROWTYPE;
n_tmptbl tmptbl;
I tried this code but can't get it thru compiler . Is there a way to store the results of ref cursor into a table ?
NOTE-I need a table because i need to access the column of ref cursor . Using dbms_sql to access records of ref cursor is a bit tough for me .
UPDATE :
/* Formatted on 8/1/2013 4:09:08 PM (QP5 v5.115.810.9015) */
CREATE OR REPLACE PROCEDURE proc_deduplicate (p_tblname IN VARCHAR2,
p_cname IN VARCHAR2,
p_cvalue IN VARCHAR2)
IS
v_cnt NUMBER;
TYPE ref_cur IS REF CURSOR;
ref_cur_name ref_cur;
v_str1 VARCHAR2 (4000);
v_str2 VARCHAR2 (4000);
v_str3 VARCHAR2 (4000);
BEGIN
v_str1 :=
'SELECT ROWID v_rowid FROM '
|| p_tblname
|| ' WHERE '
|| p_cname
|| '='''
|| p_cvalue
|| '''';
BEGIN
v_str2 :=
'SELECT COUNT ( * )
FROM '
|| p_tblname
|| ' WHERE '
|| p_cname
|| ' = '''
|| p_cvalue
|| '''';
logerrors ('proc_deduplicate',
'count exception',
SQLCODE,
v_str2 || SQLERRM,
'e');
EXECUTE IMMEDIATE v_str2 INTO v_cnt;
EXCEPTION
WHEN OTHERS
THEN
logerrors ('proc_deduplicate',
'count exception',
SQLCODE,
SQLERRM,
'e');
END;
IF v_cnt IS NOT NULL
THEN
OPEN ref_cur_name FOR v_str1;
LOOP
IF v_cnt = 1
THEN
EXIT;
ELSE
BEGIN
v_str3 :=
'DELETE FROM '
|| p_tblname
|| ' WHERE ROWID = v_rowid ';
-- THIS IS THE PROBLEM . i just created an alias above for rowid keyword but i guess, DBMS sql will have to be used after all .
EXECUTE IMMEDIATE v_str3;
EXCEPTION
WHEN OTHERS
THEN
logerrors (
' proc_deduplicate
',
' delete exception
',
SQLCODE,
SQLERRM,
' e
'
);
END;
END IF;
v_cnt := v_cnt - 1;
END LOOP;
END IF;
EXCEPTION
WHEN OTHERS
THEN
logerrors (
' proc_deduplicate',
' final exception
',
SQLCODE,
SQLERRM,
' e'
);
END;
/
By issuing TYPE ref_cur IS REF CURSOR you are declaring a weak cursor. Weak cursors return no specified types. It means that you cannot declare a variable that is of weak_cursor%rowtype, simply because a weak cursor does not return any type.
declare
type t_rf is ref cursor;
l_rf t_rf;
type t_trf is table of l_rf%rowtype;
l_trf t_trf;
begin
null;
end;
ORA-06550: line 4, column 27:
PLS-00320: the declaration of the type of this expression is incomplete or malformed
ORA-06550: line 4, column 3:
PL/SQL: Item ignored
If you specify return type for your ref cursor, making it strong, your PL/SQL block will compile successfully:
SQL> declare -- strong cursor
2 type t_rf is ref cursor return [table_name%rowtype][structure];
3 l_rf t_rf;
4 type t_trf is table of l_rf%rowtype;
5 l_trf t_trf;
6 begin
7 null;
8 end;
9 /
PL/SQL procedure successfully completed
As far as I understand what you're doing, you just need to parameterise the delete:
...
v_str3 VARCHAR2 (4000);
v_rowid ROWID;
BEGIN
...
OPEN ref_cur_name FOR v_str1;
LOOP
FETCH ref_cur_name INTO v_rowid;
EXIT WHEN ref_cur_name%NOTFOUND;
IF v_cnt = 1
THEN
EXIT;
ELSE
BEGIN
v_str3 :=
'DELETE FROM '
|| p_tblname
|| ' WHERE ROWID = :v_rowid ';
EXECUTE IMMEDIATE v_str3 USING v_rowid;
...
You need to fetch the ref_cur_name into a variable, which needs to be declared obviously, and then use that as a bind variable value in the delete.
You should do the same thing with the p_cvalue references in the other dynamic SQL too. You could probably make this much simpler, with a single delete and no explicit count, in a single dynamic statement:
CREATE OR REPLACE PROCEDURE proc_deduplicate (p_tblname IN VARCHAR2,
p_cname IN VARCHAR2,
p_cvalue IN VARCHAR2)
IS
BEGIN
execute immediate 'delete from ' || p_tblname
|| ' where ' || p_cname || ' = :cvalue'
|| ' and rowid != (select min(rowid) from ' || p_tblname
|| ' where ' || p_cname || ' = :cvalue)'
using p_cvalue, p_cvalue;
END proc_deduplicate;
/
SQL Fiddle.
If you wanted to know or report how many rows were deleted, you could refer to SQL%ROWCOUNT after the execute immediate.
Strong ref cursor returns defined values, but weak is free to anything, totally dynamic.
But we can't define rowtype variable on weak ref cur
e.g.
declare
refcur sys_refcursor;
emprec refcur%rowtype; --it'll yield error
while
declare
type empref is ref cursor returns employees%rowtype;
empcur empref;
emprec empcur%rowtype; --it'll work fine.
This is very useful, now we can define collection on them and many other advantage if you talk about practically.
No. You're trying to declare the type against an instance of the cursor anyway, so you'd be closer with:
TYPE tmptbl IS TABLE OF ref_cur%ROWTYPE;
but you still can't do that, you'd get PLS-00310: with %ROWTYPE attribute, 'REF_CUR' must name a table, cursor or cursor-variable.
A ref cursor is weakly-typed, so the compiler doesn't know what a record would look like. You could open the ref cursor for different results depending on the logic in the block, or a dynamic query, and the compiler would have no way to know what to expect in advance.
The PL/SQL documentation state that %rowtype can apply to an explicit cursor, a strong cursor variable, or a table or view. And this compares strong and weak cursor variables.
If you know what your query will be you can declare a record type with those fields, or against a table %rowtype if you're be querying a single table. Since you're using dbms_sql I guess you won't know that though. Maybe if you updated your question with more information about what you're actually trying to do there would be some other approaches you could try.

How to add a table name in EXECUTE IMMEDIATE query?

I have one question about "EXECUTE IMMEDIATE".
I have dynamicly changed table name in next plsql statement
DECLARE
TYPE CurTyp IS REF CURSOR;
cur CurTyp;
str1 VARCHAR2(30);
str2 VARCHAR2(30);
table_name VARCHAR2(30);
BEGIN
select data
into table_name
from ref
where o_id = 111
and a_id = 222;
OPEN cur FOR
'select name, sname from :1 b,myobjects a where a.obj_id = b.obj_id'
USING table_name;
LOOP
FETCH cur INTO str1, str2;
EXIT WHEN cur%NOTFOUND;
dbms_output.put_line(str1||str2);
END LOOP;
CLOSE cur;
END
Is it possible to read the result of next Execute Immediate query to cursor?
'select name, sname from :1 b,myobjects a where a.obj_id = b.obj_id'
USING table_name;
Or maybe is there any way to do this?
Thanks in advance.
For object names you must use concatenation, not bind variables.
From the Dynamic SQL chapter of the PL/SQL Language Reference:
The database uses the values of bind variables exclusively and does
not interpret their contents in any way.
Bind variables help with security and performance. But they won't work with objects like tables. If you pass in a table name then Oracle must interpret the contents, which will negate the security and performance benefits.
You can use ref_cursor
see http://docs.oracle.com/cd/B10500_01/appdev.920/a96590/adg09dyn.htm
the example:
CREATE OR REPLACE PROCEDURE query_invoice(
month VARCHAR2,
year VARCHAR2) IS
TYPE cur_typ IS REF CURSOR;
c cur_typ;
query_str VARCHAR2(200);
inv_num NUMBER;
inv_cust VARCHAR2(20);
inv_amt NUMBER;
BEGIN
query_str := 'SELECT num, cust, amt FROM inv_' || month ||'_'|| year
|| ' WHERE invnum = :id';
OPEN c FOR query_str USING inv_num;
LOOP
FETCH c INTO inv_num, inv_cust, inv_amt;
EXIT WHEN c%NOTFOUND;
-- process row here
END LOOP;
CLOSE c;
END;
/
but as #jonearles said, you can't insert the table names as params