Dynamic sql with bind variable - sql

/
create or replace procedure search_proc(p_string varchar2,p_table varchar2,p_col varchar2,search_result OUT sys_refcursor)
is
SQL_QRY VARCHAR2(2000);
BEGIN
SQL_QRY:='SELECT EMPNO,:1 FROM :2';
--DBMS_OUTPUT.PUT_LINE('SQL:'||SQL_QRY);
OPEN SEARCH_RESULT FOR SQL_QRY USING p_col,p_table;
END;
/
VARIABLE REFC REFCURSOR;
EXEC SEARCH_PROC('TEST','EMP','ENAME',:REFC);
PRINT REFC;
/
I am trying to return empno and employee name using a procedure which contains dynamically built SQL query .The query is built using bind variables.but getting the following error.May be something is wrong with the way i am calling the procedure
ORA-06512: at line 1 00903. 00000 - "invalid table name"

You can't use bind variables to take the place of identifiers, such as table names or column names. Those things must be known at the time the statement is parsed, which occurs before bind variables are bound to values. (Part of the whole purpose of using bind variables is to be able to parse a statement once then execute it with variable values.)
In this case, the solution is simple, since you are already putting the query string into a variable.
BEGIN
SQL_QRY:='SELECT EMPNO,' || p_col || ' FROM ' || p_table;
--DBMS_OUTPUT.PUT_LINE('SQL:'||SQL_QRY);
OPEN SEARCH_RESULT FOR SQL_QRY;

Related

Does Oracle SQL support output parameters in system functions?

In Oracle SQL, do any of the system functions (listed on http://docs.oracle.com/database/121/SQLRF/functions001.htm) support output parameters, or do they only return a value?
I know that in Oracle PL/SQL, a function can return values and can contain output parameters, but do any SQL system functions themselves have output parameters?
[TL;DR] Functions with out parameters can only be used in PL/SQL.
As an example:
CREATE FUNCTION test_out(
value OUT NUMBER
) RETURN NUMBER
IS
BEGIN
value := 1;
RETURN 2;
END;
/
You can run this in PL/SQL:
SET SERVEROUTPUT ON;
DECLARE
a NUMBER;
b NUMBER;
BEGIN
a := test_out( b );
DBMS_OUTPUT.PUT_LINE( a || ', ' || b );
END;
/
Outputs 2, 1
However, trying to do the same thing in SQL (creating a bind variable via Oracle's SQL Developer):
VARIABLE a NUMBER;
SELECT test_out( :a ) FROM DUAL;
PRINT a;
Gives you the exception:
SQL Error: ORA-06572: Function TEST_OUT has out arguments
06572. 00000 - "Function %s has out arguments"
*Cause: A SQL statement references either a packaged, or a stand-alone,
PL/SQL function that contains an OUT parameter in its argument
list. PL/SQL functions referenced by SQL statements must not
contain the OUT parameter.
*Action: Recreate the PL/SQL function without the OUT parameter in the
argument list.

Oracle SQL Developer, using dynamic SQL in function

I have the following script which contains a function named 'myFunction'. (declaration of types named rowValueTmp and rowValueTable are also attached for your information) Basically, I need to use a table name as an input parameter for myFunction. I found that I need to use dynamic SQL in order to use the table name as a parameter (Please correct me if there are alternative ways to do this). So the following code is what I have tried so far.
create or replace type rowValueTmp as object (
month number,
year number
);
/
create or replace type rowValueTable as table of rowValueTmp;
/
create or replace FUNCTION myFunction (TABLENAME in VARCHAR2)
return rowValueTable as
v_ret rowValueTable;
begin
execute immediate '
select rowValueTmp(month, year)
bulk collect into v_ret
from '||TABLENAME;
return v_ret;
end myFunction;
/
select * from table(myFunction('SCHEMA.TEST'));
But, this code gives me an error, and I assumed that this error is occurred because of using 'bulk collect' in execute immediate block.
ORA-03001: unimplemented feature
If I replace the content of execute immediate as the following, the above script is working..
select rowValueTmp(month, year)
bulk collect into v_ret
from SCHEMA.TEST;
Question
1] Is there any way(rather than Dynamic SQL) that I can use a table name as an input parameter for myFunction?
2] If I am not allowed to use bulk collect in execute immediate block, what do you suggest?
You can return values from execute immediately into a bulk collect:
CREATE OR REPLACE FUNCTION myfunction (tablename IN VARCHAR2)
RETURN rowvaluetable AS
v_ret rowvaluetable;
v_table VARCHAR2 (61) := DBMS_ASSERT.sql_object_name (tablename);
BEGIN
EXECUTE IMMEDIATE '
select rowValueTmp(month, year)
from ' || v_table
BULK COLLECT INTO v_ret;
RETURN v_ret;
END myfunction;
/
In the interest of an abundance of caution, I'd recommend using DBMS_ASSERT to validate the table parameter as well (as shown).

How to execute a stored procedure with cursor and table OUT parameters through SQL Developer?

I’m having trouble testing, in SQL Developer 3.2.20.09, an Oracle stored procedure that contains 2 specificities:
a user defined "cursor type" output parameter
a user defined "TABLE OF VARCHAR type" output parameter.
Stored procedure signature:
TYPE ref_cursor_tst IS REF CURSOR;
TYPE arrWarningCode_tst IS TABLE OF VARCHAR2 (4000)
INDEX BY BINARY_INTEGER;
PROCEDURE SP_ITF_CU_DOCUMENT_Test (
p_projectNumber IN VARCHAR2,
p_tag IN VARCHAR2,
p_title IN VARCHAR2,
out_document_curs OUT ref_cursor_tst,
out_errorCode OUT VARCHAR2,
out_arrWarningCode OUT arrWarningCode_tst);
My actual best test code I could end up with:
set serveroutput on size 100000
DECLARE
docRef VARCHAR2(200);
outDocCurs PD360BADMIN.PKG_ITF_GENERAL_TST.ref_cursor_tst;
outErrorCode VARCHAR2(2000);
arrWarningCodes PD360BADMIN.PKG_ITF_GENERAL_TST.arrWarningCode_tst;
i PLS_INTEGER;
doc TBL_OBJECT%ROWTYPE;
BEGIN
dbms_output.put_line('debut de procedure');
docRef:= 'DOC-012';
arrWarningCodes.DELETE;
--call SP
PKG_ITF_GENERAL_TST.SP_ITF_CU_DOCUMENT_TEST (
p_projectNumber => 'XXX',
p_tag => docRef,
p_title => 'Doc title',
out_document_curs => outDocCurs,
out_errorCode => outErrorCode,
out_arrWarningCode => arrWarningCodes);
--print error code
dbms_output.put_line('out_errorCode=' || outErrorCode);
--print output cursor
--dbms_output.put_line(outDocCurs);
LOOP
FETCH outDocCurs INTO doc;
EXIT WHEN outDocCurs%NOTFOUND;
dbms_output.put_line(doc.OBJ_ID||','||doc.OBJ_TAG);
END LOOP;
--print warnings array
IF arrWarningCodes.count > 0 THEN
FOR i IN arrWarningCodes.FIRST .. arrWarningCodes.LAST LOOP
dbms_output.put_line('warning code=' || arrWarningCodes(i) );
END LOOP;
ENd IF;
dbms_output.put_line('fin de procedure');
END;
/
The error I get:
Error report:
ORA-06504: PL/SQL: Return types of Result Set variables or query do not match
ORA-06512: at line 30
06504. 00000 - "PL/SQL: Return types of Result Set variables or query do not match"
*Cause: Number and/or types of columns in a query does not match declared
return type of a result set variable, or declared types of two Result
Set variables do not match.
*Action: Change the program statement or declaration. Verify what query the variable
actually refers to during execution.
debut de procedure
out_errorCode=
I've been testing various solutions and syntaxes for days as well as digging the net and requiring help from different sources with no success.
Any clue would be much appreciated.
Assuming TBL_OBJECT is a table of some object type which has the two fields obj_id and obj_tag; and the procedure is currently doing something like:
open out_document_curs for select * from tbl_object;
... then there are two ways to make this work. The first is to change the variables you're fetching into to match the object fields, rather than the object itself:
DECLARE
...
-- doc TBL_OBJECT%ROWTYPE;
doc_obj_id TBL_OBJECT.OBJ_ID%TYPE;
doc_obj_tag TBL_OBJECT.OBJ_TAG%TYPE;
BEGIN
...
and then change the fetch and display:
LOOP
FETCH outDocCurs INTO doc_obj_id, doc_obj_tag;
EXIT WHEN outDocCurs%NOTFOUND;
dbms_output.put_line(doc_obj_id||','||doc_obj_tag);
END LOOP;
If the object has more fields then you'd need to define them all and specify them in the fetch too.
The other is to modify the procedure so that it returns an object type:
open out_document_curs for select value(t) from tbl_object t;
Then your calling anonymous block will work as it is, as the simple query will return the object itself rather than the fields within it.
Which you do will depend on how the procedure will really be used, rather than your test call.
With some dummy set-up:
create type doc_obj as object (obj_id number, obj_tag varchar2(10));
/
create table tbl_object of doc_obj;
insert into tbl_object values (doc_obj(1, 'Test'));
And a dummy package body with the procedure simplified as:
PROCEDURE SP_ITF_CU_DOCUMENT_Test (
p_projectNumber IN VARCHAR2,
p_tag IN VARCHAR2,
p_title IN VARCHAR2,
out_document_curs OUT ref_cursor_tst,
out_errorCode OUT VARCHAR2,
out_arrWarningCode OUT arrWarningCode_tst)
IS
BEGIN
open out_document_curs for select value(o) from tbl_object o;
out_errorCode := 'OK';
out_arrWarningCode(1) := 'Danger!';
END SP_ITF_CU_DOCUMENT_Test;
Then calling your code exactly as you have it in the question (minus the schema name) gives:
anonymous block completed
debut de procedure
out_errorCode=OK
1,Test
warning code=Danger!
fin de procedure
Using the other approach, with the individual variables for the object fields, gives the same result too.

In a PL/SQL procedure, how do I pass a table name as a parameter?

CREATE PROCEDURE A(tab IN <table - what should I write here?>) AS
BEGIN
INSERT INTO tab VALUES(123);
END A;
How can I specify that the parameter tab is a table name?
You can't. Instead you need to pass it in as a VARCHAR2 string and then use Dynamic SQL:
CREATE PROCEDURE A(tab IN VARCHAR2) AS
BEGIN
EXECUTE IMMEDIATE 'INSERT INTO ' || tab || 'VALUES(123)';
END A;
Read up about Dynamic SQL and be aware of the issues it can bring if used unwisely, such as poorer performance, scalability and security.

Oracle Stored Procedure with Alter command

I am trying to build an Oracle stored procedure which will accept a table name as a parameter. The procedure will then rebuild all indexes on the table.
My problem is I get an error while using the ALTER command from a stored procedure, as if PLSQL does not allow that command.
Use the execute immediate statement to execute DDL inside PL/SQL.
create procedure RebuildIndex(index_name varchar2) as
begin
execute immediate 'alter index ' || index_name || ' rebuild';
end;
I tested this code; it works.
Documentation.
Passing Schema Object Names As Parameters
Suppose you need a procedure that
accepts the name of any database
table, then drops that table from your
schema. You must build a string with a
statement that includes the object
names, then use EXECUTE IMMEDIATE to
execute the statement:
CREATE TABLE employees_temp AS SELECT last_name FROM employees;
CREATE PROCEDURE drop_table (table_name IN VARCHAR2) AS
BEGIN
EXECUTE IMMEDIATE 'DROP TABLE ' || table_name;
END;
/
Use concatenation to build the string,
rather than trying to pass the table
name as a bind variable through the
USING clause.
In addition, if you need to call a
procedure whose name is unknown until
runtime, you can pass a parameter
identifying the procedure. For
example, the following procedure can
call another procedure (drop_table) by
specifying the procedure name when
executed.
CREATE PROCEDURE run_proc (proc_name IN VARCHAR2, table_name IN VARCHAR2) ASBEGIN
EXECUTE IMMEDIATE 'CALL "' || proc_name || '" ( :proc_name )' using table_name;
END;
/
If you want to drop a table with the
drop_table procedure, you can run the
procedure as follows. Note that the
procedure name is capitalized.
CREATE TABLE employees_temp AS SELECT last_name FROM employees;
BEGIN
run_proc('DROP_TABLE', 'employees_temp');
END;
/
Here are a couple of possibilities. First, you would have to treat the SQL as dynamic SQL. Second, Oracle DDL statements cannot be run in a transaction (or, they terminate the current transaction and cannot themselves be rolled back). This may affect whether you can use them in stored procedures, or where you can use stored procedures that contain them.
If none of the above apply at all - there could easily be something else astray - I suggest posting some code.