Pass SQL string to oracle stored procedure and get results with execute immediate - sql

I am trying to pass in a SQL string to a stored procedure and using EXECUTE IMMEDIATE to return the results. Something like this:
CREATE PROCEDURE P360_RCT_COUNT (sqlString IN VARCHAR2)
AS
BEGIN
EXECUTE IMMEDIATE sqlString;
END;
/
I am not sure how to accomplish it. With the above, when I execute the SP using the command below, I get an error:
EXECUTE P360_RCT_COUNT 'SELECT COUNT(DISTINCT ENTITY_ID),ADDR_COUNTY FROM P360_V_RCT_COUNT GROUP BY ADDR_COUNTY';
The error is: ORA-06550: line 1, column 22:
PLS-00103: Encountered the symbol "SELECT COUNT(ENTITY_ID),ADDR_COUNTY
FROM P360_V_RCT_COUNT GROUP " when expecting one of the following:
:= . ( # % ; The symbol ":=" was substituted for "SELECT
COUNT(DISTINCT ENTITY_ID),ADDR_COUNTY FROM P360_V_RCT_COUNT GROUP " to
continue.
Basically I am building a SQL string in a system and need to pass it in to the SP and get the results back to the system. I am relatively new to stored procedures in Oracle.

The easiest way to work with a result set is sys_refcursor. This can be used quite easily with JDBC or ODBC.
Your procedure would look like this:
CREATE PROCEDURE P360_RCT_COUNT (
sqlString IN VARCHAR2
, p_result_set out sys_refcursor)
AS
BEGIN
open p_result_set for sqlString;
END;
/
Obviously the precise details of how you call it will vary according to your client. But in SQL*Plus it would be:
var rc refcursor
exec P360_RCT_COUNT( 'SELECT COUNT(DISTINCT ENTITY_ID),ADDR_COUNTY FROM P360_V_RCT_COUNT GROUP BY ADDR_COUNTY', :rc);
print rc

To return lists of values in a OUT parameter you need to decide the type(s) to use.
Say, for example, you have to return some varchar2 and some date lists, you could use something like this:
create or replace type tabOfVarchar2 is table of varchar2(100);
create or replace type tabOfDates is table of date;
create or replace procedure testProc(pString IN varchar2,
pOutVarchar1 OUT tabOfVarchar2,
pOutVarchar2 OUT tabOfVarchar2,
pOutVarchar3 OUT tabOfVarchar2,
pOutDates OUT tabOfDates
) is
begin
execute immediate pString
bulk collect into pOutVarchar1, pOutVarchar2, pOutVarchar3, pOutDates;
end;
This is way you can test this procedure:
declare
v1 tabOfVarchar2 ;
v2 tabOfVarchar2;
v3 tabOfVarchar2;
d1 tabOfDates ;
vSQL varchar2(100) := 'select ''a'', ''b'', ''c'', sysdate from dual';
begin
testProc(vSQL, v1, v2, v3, d1);
--
for i in v1.first .. v1.last loop
dbms_output.put_line(v1(i) || '/' || v2(i) || '/' || v3(i) || '/' || to_char(d1(i), 'dd/mm/yyyy'));
end loop;
end;
which gives:
a/b/c/14/04/2017
This only works with queries that give exactly a fixed number of columns, of known types.

Related

The dynamic sql in the stored procedure

I would like to pass in a dynamic column name to calculate its standard deviation,in oracle, the following is the code:
CREATE OR REPLACE
PROCEDURE ReportCalculate
(
param_columnName IN VARCHAR2 DEFAULT 'COMPUTED_CP_MLIFTOFF_KNOT9OR10'
)
AS
sqlstr VARCHAR2(500);
result NUMBER;
BEGIN
sqlstr:='select stddev(:col) from LIDISUDUXIAO where 1=1 and NO_10_LIMITSIGN_DEPART_ID<>0';
execute immediate sqlstr into result using param_columnName;
DBMS_OUTPUT.PUT_LINE(result);
END;
I call it with the default parameters,the error message is:
Procedure execution failed
ORA-01722: invalid number
ORA-06512: at "AGS.REPORTCALCULATE", line 10
ORA-06512: at line 1
How can i solve it?
A column name cannot be used as parameter of a query. You need to edit the sqlstr variable to include the provided param_columnName in the query itself before executing it:
CREATE OR REPLACE
PROCEDURE ReportCalculate
(
param_columnName IN VARCHAR2 DEFAULT 'COMPUTED_CP_MLIFTOFF_KNOT9OR10'
)
AS
sqlstr VARCHAR2(500);
result NUMBER;
BEGIN
sqlstr:='select stddev(' || param_columnName || ') from LIDISUDUXIAO where 1=1 and NO_10_LIMITSIGN_DEPART_ID<>0';
execute immediate sqlstr into result;
DBMS_OUTPUT.PUT_LINE(result);
END;

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).

Executing Dynamic Native SQL with Oracle Table type gives invalid identifier error

We have created Oracle Table type. And we have created an array of it. This is done as we dont know how many values can come which may be too much for a IN clause in sql query.
--Code Snippet -----
create or replace
TYPE "INPUTCODE"
as object
(pc varchar2(100) )
create or replace
TYPE "INPUTCODEARR"
IS TABLE OF inputcode;
create or replace
PROCEDURE "TEST_PROC" (testCodes IN inputcodeArr, timeHorizon IN NUMBER, p_recordset OUT SYS_REFCURSOR)
AS
var_sqlStmt VARCHAR2(4096);
BEGIN
var_sqlStmt := 'select t.a,t.b, t.c';
var_sqlStmt := var_sqlStmt || 'from test t';
if testCodes is not null then
var_sqlStmt := var_sqlStmt || ', table(testCodes) tc';
var_sqlStmt := var_sqlStmt || 'where tc.pc = t.name';
end if;
dbms_output.put_line('Final SQL Statement::' || var_sqlStmt);
open p_recordset for var_sqlStmt;
END TEST_PROC;
All the above ones are compiles and when you run the TEST_PROC procedure with few testCode values it will fail with error Invalid identifier for testCodes.
In the procedure, final sql statement which is printing in my console is correct and when you run this as a static sql statement inside procedure it runs without any error. But inside the dynamic sql it fails.
I tried executing using DYNAMIC_SQL package, but it results in same error.
Also, i tried giving it as a bind variable for 'table(testCodes)'. That also failed.
Please suggest.
You are using dynamic SQL, so you must tell Oracle which word is an identifier and which word is a variable.
Consider the following statement running directly in SQLPlus:
select t.a,t.b, t.c from test t, table(testCodes) tc
It will fail because no object is named testCodes in your DB. You have to tell the SQL engine that testCodes is in fact a variable. You have to do this because you have chosen to use dynamic SQL whereas variable binding is automatic in static SQL.
In most cases, you can bind "object" variables in the same way as standard variables. In PL/SQL there are several ways to do this, for instance with cursors you would use USING:
SQL> DECLARE
2 l_cur SYS_REFCURSOR;
3 l_tab inputcodeArr := inputcodeArr(INPUTCODE('A'), INPUTCODE('B'));
4 l_obj varchar2(100);
5 BEGIN
6 OPEN l_cur FOR 'SELECT pc FROM TABLE(:my_variable)' -- notice the ":"
7 USING l_tab; -- binding by position
8 LOOP
9 FETCH l_cur
10 INTO l_obj;
11 EXIT WHEN l_cur%NOTFOUND;
12 dbms_output.put_line(l_obj);
13 END LOOP;
14 CLOSE l_cur;
15 END;
16 /
A
B
PL/SQL procedure successfully completed
In your case however I wouldn't bother with dynamic SQL since you can open a cursor conditionally:
CREATE OR REPLACE PROCEDURE "TEST_PROC"(testCodes IN inputcodeArr,
timeHorizon IN NUMBER,
p_recordset OUT SYS_REFCURSOR) AS
BEGIN
IF testCodes IS NOT NULL THEN
OPEN p_recordset FOR
SELECT t.a, t.b, t.c FROM test t, TABLE(testCodes) tc
WHERE tc.pc = t.NAME;
ELSE
OPEN p_recordset FOR
SELECT t.a, t.b, t.c FROM test t;
END IF;
END TEST_PROC;
My advice would be to stick with static SQL as long as possible, as it's a lot easier to make mistakes with dynamic SQL.
Update following comment:
If your number of input is not constant and you have to use dynamic SQL because there are many combinations of filters, you can use the following strategy:
CREATE OR REPLACE PROCEDURE "TEST_PROC"(testCodes IN inputcodeArr,
timeHorizon IN NUMBER,
p_recordset OUT SYS_REFCURSOR) AS
l_sql LONG := 'SELECT t.a, t.b, t.c FROM test t WHERE';
BEGIN
-- filter #1
IF testCodes IS NOT NULL THEN
l_sql := l_sql || ' t.name IN (SELECT pc FROM TABLE(:filter1))';
ELSE
l_sql := l_sql || ' :filter1 IS NULL';
END IF;
-- filter #2
IF timeHorizon IS NOT NULL THEN
l_sql := l_sql || ' AND t.horizon = :filter2';
ELSE
l_sql := l_sql || ' AND :filter2 IS NULL';
END IF;
-- open cursor
OPEN p_recordset FOR l_sql USING testCodes, timeHorizon;
END TEST_PROC;
/
I'm making sure that the final SQL will always have the same number of variables in the same order, however each condition where the filter is NULL will be a tautology (NULL IS NULL).

Error in Parameters of Stored Procedure when Dynamic SQL is used

I am using Oracle Sql Developer to write a Stored Proc, which accepts a list of values separated by "," (dynamic sql) and use this variable in "in" clause to fire a query from a table.
But i am ending up with an error at
#p_case_nbr varchar2(100),
Error(4,5): PLS-00103: Encountered the symbol "#" when expecting one of the following: current delete exists prior
The SP used:
create or replace
procedure TEST_PROC
(
#p_case_nbr varchar2(100),
p_out_case_nbr out varchar2
)
as
cursor test_cur is
select t.COLUMN1
from test_table t
where t.column1 in (#p_case_nbr);
test_cur_line test_cur%ROWTYPE;
begin
p_out_case_nbr := null;
open test_cur;
LOOP
FETCH test_cur INTO test_cur_line;
EXIT WHEN test_cur%NOTFOUND;
p_out_case_nbr := p_out_case_nbr || ' ' || test_cur_line.COLUMN1;
dbms_output.put_line(test_cur_line.COLUMN1);
end loop;
close test_cur;
dbms_output.put_line('Final Value ' || p_out_case_nbr);
dbms_output.put_line('SP Completed Succesfully');
end;
In ORACLE no need of giving # before varibles
Just try to remove the # from #p_case_nbr where ever specified and try once again
and also no need of specifying the size of the parameter like #p_case_nbr varchar2(100)
just give #p_case_nbr varchar

pl/sql - to_date not working with execute immediate parameter

i wanna be able to execute my below proc like so:
exec procname('29-JAN-2011');
proc code is:
PROCEDURE procname(pardate VARCHAR2) IS
vardate DATE := to_date(pardate, 'DD-MON-YYYY');
SQLS VARCHAR2(4000);
BEGIN
SQLS := 'SELECT cola, colb
FROM tablea
WHERE TRUNC(coldate) = TRUNC(TO_DATE('''||pardate||''',''DD/MON/YYYY''))';
EXECUTE IMMEDIATE SQLS;
END;
It keeps throwing error:
ORA-00904: "JAN": invalid identifier.
It compiles, but it throws the error when I run this command:
EXEC procname('29-JAN-2011');
You declare a variable which casts the input parameter to a date: why not use it?
Also, the TRUNC() applied to a date removes the time element. You don't need it here because the value you're passing has no time.
So, your code should be:
PROCEDURE procname(pardate VARCHAR2) IS
vardate DATE := to_date(pardate, 'DD-MON-YYYY');
SQLS VARCHAR2(4000) := 'select cola, colb FROM tablea
WHERE TRUNC(coldate) = :1';
l_a tablea.cola%type;
l_b tablea.colb%type;
BEGIN
EXECUTE IMMEDIATE SQLS
into l_a, l_b
using vardate;
END;
Specifying the dynamic SQL statement with a bind variable and executing it with the USING syntax is a lot more efficient. Note that we still have to SELECT into some variables.
You're using two different notations in the two calls to to_date. I think one of them (the second) is wrong.