How to add a table name in EXECUTE IMMEDIATE query? - sql

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

Related

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

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!!

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 Select from table at database link

I want to select data from a table in a remote database which I have its database link in a variable, how I can do that?
My query is something like this:
select `table_column` form any_table#:any_variable_1 where any_column= :any_variable_2;
Note:
any_variable_1 is a string variable contains the name of the database link
any_variable_2 is a string variable contains the string for filtering
* this code is to be executed in function in powerbuilder 8 or 9
* the database which I am connecting to is oracle 11 g
Try this:
DECLARE
l_var number(10); -- data type you are expecting from the table_column column
l_cursor sys_refcursor;
l_any_variable_1 varchar2(50) := 'YOUR_DBLINK_HERE';
l_any_variable_2 varchar2(50);
BEIGN
open l_cursor for 'select table_column from any_table# '||
l_any_variable_1 ||
' where any_column = :any_variable_2'
using l_any_variable_2;
loop
fetch l_cursor into l_var;
exit when l_cursor%NOTFOUND;
DBMS_OUTPUT.put_line(l_var);
end loop;
close l_cursor;
END;
string ls_sql
ls_sql="Select column_name from table_name#"+var_db_link_name+""+&
" where column_name='"+var_value+"' and another_column_name='"+another_var+"'"
DECLARE var_cursor DYNAMIC CURSOR FOR SQLSA ;
PREPARE SQLSA FROM :ls_sql ;
OPEN DYNAMIC var_cursor ;
FETCH var_cursor INTO :another_var ;//another_var holds the result of the cursor
CLOSE var_cursor;

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;

Looping on values, creating dynamic query and adding to result set

I have the following problem. I am an experienced Java programmer but am a bit of a n00b at SQL and PL/SQL.
I need to do the following.
1 Pass in a few arrays and some other variables into a procedure
2 Loop on the values in the arrays (they all have the same number of items) and dynamically create an SQL statement
3 Run this statement and add it to the result set (which is an OUT parameter of the procedure)
I already have experience of creating an SQL query on the fly, running it and adding the result to a result set (which is a REF CURSOR) but I'm not sure how I'd loop and add the results of each call to the query to the same result set. I'm not even sure if this is possible.
Here's what I have so far (code edited for simplicity). I know it's wrong because I'm just replacing the contents of the RESULT_SET with the most recent query result (and this is being confirmed in the Java which is calling this procedure).
Any and all help would be greatly appreciated.
TYPE REF_CURSOR IS REF CURSOR;
PROCEDURE GET_DATA_FASTER(in_seq_numbers IN seq_numbers_array, in_values IN text_array, in_items IN text_array, list IN VARCHAR2, RESULT_SET OUT REF_CURSOR) AS
query_str VARCHAR2(4000);
seq_number NUMBER;
the_value VARCHAR2(10);
the_item VARCHAR2(10);
BEGIN
FOR i IN 1..in_seq_numbers.COUNT
LOOP
seq_number := in_seq_numbers(i);
the_value := trim(in_values(i));
the_item := trim(in_items(i));
query_str := 'SELECT distinct '||seq_number||' as seq, value, item
FROM my_table ai';
query_str := query_str || '
WHERE ai.value = '''||the_value||''' AND ai.item = '''||the_item||'''
AND ai.param = ''BOOK''
AND ai.prod in (' || list || ');
OPEN RESULT_SET FOR query_str;
END LOOP;
EXCEPTION WHEN OTHERS THEN
RAISE;
END GET_DATA_FASTER;
A pipelined table function seems a better fit for what you want, especially if all you're doing is retrieving data. See http://www.oracle-base.com/articles/misc/pipelined-table-functions.php
What you do is create a type for your output row. So in your case you would create an object such as
CREATE TYPE get_data_faster_row AS OBJECT(
seq NUMBER(15,2),
value VARCHAR2(10),
item VARCHAR2(10)
);
Then create a table type which is a table made up of your row type above
CREATE TYPE get_data_faster_data IS TABLE OF get_data_faster_row;
Then create your table function that returns the data in a pipelined manner. Pipelined in Oracle is a bit like a yield return in .net (not sure if you're familiar with that). You find all of the rows that you want and "pipe" them out one at a time in a loop. When your function completes the table that's returned consists of all the rows you piped out.
CREATE FUNCTION Get_Data_Faster(params) RETURN get_data_faster_data PIPELINED AS
BEGIN
-- Iterate through your parameters
--Iterate through the results of the select using
-- the current parameters. You'll probably need a
-- cursor for this
PIPE ROW(get_data_faster_row(seq, value, item));
LOOP;
LOOP;
END;
EDIT: Following Alex's comment below, you need something like this. I haven't been able to test this but it should get you started:
CREATE FUNCTION Get_Data_Faster(in_seq_numbers IN seq_numbers_array, in_values IN text_array, in_items IN text_array, list IN VARCHAR2) RETURN get_data_faster_data PIPELINED AS
TYPE r_cursor IS REF CURSOR;
query_results r_cursor;
results_out get_data_faster_row := get_data_faster_row(NULL, NULL, NULL);
query_str VARCHAR2(4000);
seq_number NUMBER;
the_value VARCHAR2(10);
the_item VARCHAR2(10);
BEGIN
FOR i IN 1..in_seq_number.COUNT
LOOP
seq_number := in_seq_numbers(i);
the_value := trim(in_values(i));
the_item := trim(in_items(i));
query_str := 'SELECT distinct '||seq_number||' as seq, value, item
FROM my_table ai';
query_str := query_str || '
WHERE ai.value = '''||the_value||''' AND ai.item = '''||the_item||'''
AND ai.param = ''BOOK''
AND ai.prod in (' || list || ');
OPEN query_results FOR query_str;
LOOP
FETCH query_results INTO
results_out.seq,
results_out.value,
results_out.item;
EXIT WHEN query_results%NOTFOUND;
PIPE ROW(results_out);
END LOOP;
CLOSE query_results;
END LOOP;
END;
Extra info from Alex's comment below useful for the answer:
you can have multiple loops from different sources, and as long as the
data from each be put into the same object type, you can just keep
pumping them out with pipe row statements anywhere in the function.
The caller sees them as a table with the rows in the order you pipe
them. Rather than call a procedure and get a result set as an output
parameter, you can query as select seq, value, item from
table(package.get_data_faster(a, b, c, d)), and of course you can
still have an order by clause if the order they're piped isn't what
you want.