Dynamic select execution missing expression error - oracle

I am using Oracle 12, and I want to make a dynamic procedure which selects rows from specific table but according to an unknown conditio. That condition will be specified as input parameter.
Suppose I have a column called employee id and I want to call the procedure
with the following condition
execute s('employeeid = 2')
My code is
create or replace procedure s (condition varchar)
as
TYPE EmpCurTyp IS REF CURSOR; -- define weak REF CURSOR type
emp_cv EmpCurTyp; -- declare cursor variable
my_ename VARCHAR2(15);
my_sal NUMBER := 2;
mycondition varchar2(100):=condition;
BEGIN
OPEN emp_cv FOR -- open cursor variable
'SELECT employeeid, employeename FROM employees WHERE = :s' USING mycondition;
END;
but I am getting an error
missing expression
What am I doing wrong, and will the result of this procedure be selected rows from employees table that satisfy applied condition ?

The USING is meant to handle values, not pieces of code; if you need to edit your query depending on an input parameter ( and I believe this is a very dangerous way of coding), you should treat the condition as a string to concatenate to the query.
For example, say you have this table:
create table someTable(column1 number)
This procedure does somthing similar to what you need:
create or replace procedure testDyn( condition IN varchar2) is
cur sys_refcursor;
begin
open cur for 'select column1 from sometable where ' || condition;
/* your code */
end;
Hot it works:
SQL> exec testDyn('column1 is null');
PL/SQL procedure successfully completed.
SQL> exec testDyn('column99 is null');
BEGIN testDyn('column99 is null'); END;
*
ERROR at line 1:
ORA-00904: "COLUMN99": invalid identifier
ORA-06512: at "ALEK.TESTDYN", line 4
ORA-06512: at line 1

This is not embedded in a procedure yet but I tested this and works:
DECLARE
TYPE OUT_TYPE IS TABLE OF VARCHAR2 (20)
INDEX BY BINARY_INTEGER;
l_cursor INTEGER;
l_fetched_rows INTEGER;
l_sql_string VARCHAR2 (250);
l_where_clause VARCHAR2 (100);
l_employeeid VARCHAR2 (20);
l_employeename VARCHAR2 (20);
l_result INTEGER;
o_employeeid OUT_TYPE;
o_employeename OUT_TYPE;
BEGIN
l_cursor := DBMS_SQL.OPEN_CURSOR;
l_sql_string := 'SELECT employeeid, employeename FROM employees WHERE ';
l_where_clause := 'employeeid = 2';
l_sql_string := l_sql_string || l_where_clause;
DBMS_SQL.PARSE (l_cursor, l_sql_string, DBMS_SQL.V7);
DBMS_SQL.DEFINE_COLUMN (l_cursor,
1,
l_employeeid,
20);
DBMS_SQL.DEFINE_COLUMN (l_cursor,
2,
l_employeename,
20);
l_fetched_rows := 0;
l_result := DBMS_SQL.EXECUTE_AND_FETCH (l_cursor);
LOOP
EXIT WHEN l_result = 0;
DBMS_SQL.COLUMN_VALUE (l_cursor, 1, l_employeeid);
DBMS_SQL.COLUMN_VALUE (l_cursor, 2, l_employeename);
l_fetched_rows := l_fetched_rows + 1;
o_employeeid (l_fetched_rows) := l_employeeid;
o_employeename (l_fetched_rows) := l_employeename;
l_result := DBMS_SQL.FETCH_ROWS (l_cursor);
END LOOP;
DBMS_SQL.CLOSE_CURSOR (l_cursor);
DBMS_OUTPUT.PUT_LINE (o_employeeid (1));
DBMS_OUTPUT.PUT_LINE (o_employeename (1));
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.PUT_LINE ('GENERAL FAILURE: ' || SQLERRM);
END;

Related

Oracle PLSQL wrong number or types of arguments in call to 'BIND_ARRAY' in DBMS_SQL

I've written the below PLSQL block.
However when i execute it , i am getting following error.
Kindly tell me how to pass nested tables in the DBMS_SQL to
execute and generate the report.
Can we use DBMS_SQL to pass array value to bind
parameters ?
Or else do we have to use Execute Immediate only.
We are using DBMS_SQL for generating all the reports.
Error report -
ORA-06550: line 34, column 2:
PLS-00306: wrong number or types of arguments in call to 'BIND_ARRAY'
ORA-06550: line 34, column 2:
PL/SQL: Statement ignored
ORA-06550: line 35, column 2:
PLS-00306: wrong number or types of arguments in call to 'BIND_ARRAY'
ORA-06550: line 35, column 2:
declare
l_cursor number := dbms_sql.open_cursor;
l_ignore number;
q varchar2(32000);
o_desc_tab DBMS_SQL.desc_tab;
l_col_cnt number(10);
l_val VARCHAR2 (32767);
l_rec_no NUMBER;
l_col_sep VARCHAR2(25) := ',';
l_text VARCHAR2 (32767);
l_value VARCHAR2 (4000);
TYPE T_dc_code IS TABLE OF VARCHAR2(25);
A_dc_code T_dc_code;
TYPE T_bin_type IS TABLE OF VARCHAR2(25);
A_bin_type T_bin_type;
begin
q :='select col1, col2, col3, col4
from tabl1 wbm
inner join table2 wam
on wbm.dc_code=wam.dc_code
and wbm.dc_area=wam.dc_area
where wbm.dc_code MEMBER OF :P_DC_CODE
and wbm.BIN_TYPE MEMBER OF :P_BIN_TYPE
)';
dbms_sql.parse( l_cursor,q,dbms_sql.native );
A_dc_code.EXTEND(2);
A_dc_code(1) := '888';
A_dc_code(2) := '902';
A_bin_type.EXTEND(2);
A_bin_type(1) := 'R';
A_bin_type(2) := 'P';
dbms_sql.bind_array( l_cursor, ':P_DC_CODE', A_bin_type );
dbms_sql.bind_array( l_cursor, ':P_BIN_TYPE', A_dc_code );
DBMS_SQL.describe_columns (l_cursor, l_col_cnt, o_desc_tab);
FOR l_cl_cnt IN 1 .. o_desc_tab.COUNT
LOOP
DBMS_SQL.define_column (l_cursor, l_cl_cnt, l_val, 32767);
END LOOP;
l_ignore := dbms_sql.execute( l_cursor );
WHILE (DBMS_SQL.fetch_rows (l_cursor) > 0)
LOOP
DBMS_OUTPUT.PUT_LINE('COMING IN LOOP');
FOR l_rec_no IN 1 .. o_desc_tab.COUNT
LOOP
DBMS_SQL.COLUMN_VALUE (l_cursor, l_rec_no, l_value);
l_text := l_text || l_col_sep || l_value;
END LOOP;
END LOOP;
dbms_sql.close_cursor( l_cursor );
DBMS_OUTPUT.PUT_LINE('THE VALUE IS ---- '||l_text);
end;
/
Thanks
You have to use one of the pre-defined types, e.g. DBMS_SQL.varchar2_table, see Types for Scalar and LOB Collections.
Using your own TYPE T_bin_type IS TABLE OF VARCHAR2(25) is not possible
A_bin_type DBMS_SQL.varchar2_table;
BEGIN
A_bin_type(1) := 'R';
A_bin_type(2) := 'P';

Why do I get "ORA-00933: SQL command not properly ended" error ( execute immediate )?

I created a function and it uses a dynamic sql:
create function check_ref_value
(
table_name varchar2,
code_value number,
code_name varchar2
) return number is
l_query varchar2(32000 char);
l_res number;
begin
l_query := '
select sign(count(1))
into :l_res
from '|| table_name ||'
where '|| code_name ||' = :code_value
';
execute immediate l_query
using in code_value, out l_res;
return l_res;
end;
But when I try to use it I get an exception "ORA-00933: SQL command not properly ended"
What is wrong with this code?
You can use EXECUTE IMMEDIATE ... INTO ... USING ... to get the return value and DBMS_ASSERT to raise errors in the case of SQL injection attempts:
create function check_ref_value
(
table_name varchar2,
code_value number,
code_name varchar2
) return number is
l_query varchar2(32000 char);
l_res number;
begin
l_query := 'select sign(count(1))'
|| ' from ' || DBMS_ASSERT.SIMPLE_SQL_NAME(table_name)
|| ' where ' || DBMS_ASSERT.SIMPLE_SQL_NAME(code_name)
|| ' = :code_value';
execute immediate l_query INTO l_res USING code_value;
return l_res;
end;
/
Which, for the sample data:
CREATE TABLE abc (a, b, c) AS
SELECT 1, 42, 3.14159 FROM DUAL;
Then:
SELECT CHECK_REF_VALUE('abc', 42, 'b') AS chk FROM DUAL;
Outputs:
CHK
1
And:
SELECT CHECK_REF_VALUE('abc', 42, '1 = 1 OR b') AS chk FROM DUAL;
Raises the exception:
ORA-44003: invalid SQL name
ORA-06512: at "SYS.DBMS_ASSERT", line 160
ORA-06512: at "FIDDLE_UVOFONEFDEHGDQJELQJL.CHECK_REF_VALUE", line 10
As for your question:
What is wrong with this code?
Using SELECT ... INTO is only valid in an SQL statement in a PL/SQL block and when you run the statement via EXECUTE IMMEDIATE it is executed in the SQL scope and not a PL/SQL scope.
You can fix it by wrapping your dynamic code in a BEGIN .. END PL/SQL anonymous block (and reversing the order of the bind parameters in the USING clause):
create function check_ref_value
(
table_name varchar2,
code_value number,
code_name varchar2
) return number is
l_query varchar2(32000 char);
l_res number;
begin
l_query := '
BEGIN
select sign(count(1))
into :l_res
from '|| DBMS_ASSERT.SIMPLE_SQL_NAME(table_name) ||'
where '|| DBMS_ASSERT.SIMPLE_SQL_NAME(code_name) ||' = :code_value;
END;
';
execute immediate l_query
using out l_res, in code_value;
return l_res;
end;
/
(However, that is a bit more of a complicated solution that just using EXECUTE IMMEDIATE ... INTO ... USING ....)
db<>fiddle here

How to access the structure and get the column list ,datatypes of refcursor?

I have a procedure which gets me the output with refcursor and data/structure in cursor will be dynami. Each time depending on inputs datatypes and no of columns in cursor will vary. So how can I access this structure and get the datatypes ?
PROCEDURE PROC_B ( name_ IN VARCHAR2,
date_ IN DATE,
code_ IN VARCHAR2,
sp_name_ IN VARCHAR2,
wrapper_ OUT sys_refcursor,
datyapes_ OUT VARCHAR2,
TS2_ OUT VARCHAR2,
header_ OUT VARCHAR2)
AS
TS_ DATE;
BEGIN
PROC_A (name_, date_, code_, sp_name_, wrapper_, TS_, header_);
TS2_:= TO_CHAR(TS_, 'MM-DD-YYYY.HH24_MI');
-- Logic should come here for below requirement
-- Get the datatypes of variables from wrapper_ (ref cursor datatype) and send them back in datyapes_ .
-- Eg1 : If ref cursor returns 2 values with dataytpes VARCHAR2 & Num then o/p should be VARCHAR2|NUMBER ,
--Eg2 : If ref cursor returns 3 values with dataytpes DATE , TIMESTAMP , VARCHAR2 then o/p should be DATE|TS|VARCHAR2
END;**
You can convert the ref cursor to a DBMS_SQL cursor using the DBMS_SQL.TO_CURSOR_NUMBER function. Then, having the cursor number, you can inspect manipulate it via DBMS_SQL. This includes being able to describe it's columns, as shown in the example below:
DECLARE
l_rc SYS_REFCURSOR;
l_cursor_number INTEGER;
l_col_cnt INTEGER;
l_desc_tab DBMS_SQL.desc_tab;
l_col_num INTEGER;
BEGIN
OPEN l_rc FOR 'SELECT object_name, object_type, last_ddl_time FROM dba_objects where rownum <= 10';
l_cursor_number := DBMS_SQL.to_cursor_number (l_rc);
DBMS_SQL.describe_columns (l_cursor_number, l_col_cnt, l_desc_tab);
l_col_num := l_desc_tab.FIRST;
IF (l_col_num IS NOT NULL) THEN
LOOP
DBMS_OUTPUT.put_line ('Column #' || l_col_num);
DBMS_OUTPUT.put_line ('...name: ' || l_desc_tab (l_col_num).col_name);
DBMS_OUTPUT.put_line ('...type: ' || l_desc_tab (l_col_num).col_type);
DBMS_OUTPUT.put_line ('...maxlen: ' || l_desc_tab (l_col_num).col_max_len);
-- ... other fields available in l_desc_tab(l_col_num) too.
l_col_num := l_desc_tab.NEXT (l_col_num);
EXIT WHEN (l_col_num IS NULL);
END LOOP;
END IF;
DBMS_SQL.close_cursor (l_cursor_number);
END;
Output
Column #1
...name: OBJECT_NAME
...type: 1
...maxlen: 128
Column #2
...name: OBJECT_TYPE
...type: 1
...maxlen: 23
Column #3
...name: LAST_DDL_TIME
...type: 12
...maxlen: 7
Since you're on 11g, you can use the dbms_sql package to interrogate your ref cursor, and then loop over the column types. They are reported as numbers so you'll need to translate the type numbers to strings (listed here).
This is a demo to give you the idea:
set serveroutput on
DECLARE
-- mimicking your procedure arguments
wrapper_ SYS_REFCURSOR;
datyapes_ VARCHAR(100);
L_COLS NUMBER;
L_DESC DBMS_SQL.DESC_TAB;
L_CURS INTEGER;
L_VARCHAR VARCHAR2(4000);
BEGIN
-- fake cursor, instead of procedure call
open wrapper_ for q'[select 42, 'Test', date '2017-03-02' from dual]';
L_CURS := DBMS_SQL.TO_CURSOR_NUMBER(wrapper_);
DBMS_SQL.DESCRIBE_COLUMNS(C => L_CURS, COL_CNT => L_COLS,
DESC_T => L_DESC);
FOR i IN 1..L_COLS LOOP
datyapes_ := datyapes_ || CASE WHEN i > 1 THEN '|' END
|| CASE L_DESC(i).col_type
WHEN 1 THEN 'VARCHAR2'
WHEN 2 THEN 'NUMBER'
WHEN 12 THEN 'DATE'
WHEN 96 THEN 'CHAR'
WHEN 180 THEN 'TS'
-- more types as needed
ELSE 'unknown'
END;
END LOOP;
DBMS_SQL.CLOSE_CURSOR(L_CURS);
-- just for debug
dbms_output.put_line('datyapes_: ' || datyapes_);
END;
/
which gets output:
datyapes_: NUMBER|CHAR|DATE
PL/SQL procedure successfully completed.
I've kept your variable name as datyapes_ as shown in the question, but perhaps you really have datatypes_.

Dynamic procedure execution with 'table of varchar and sys_refcursor as an argument

Trying to execute a procedure dynamically with DBMS_SQL that takes 'table of varchar' and sys_refcursor as an argument using the code below:
DECLARE
TYPE CriteriaMap IS TABLE OF VARCHAR (100)
INDEX BY VARCHAR2 (100);
o_cursor SYS_REFCURSOR;
v_cid INTEGER;
v_dummy INTEGER;
v_date_to_run DATE := SYSDATE;
v_sql_execute_proc VARCHAR2 (1024);
v_filter_criteria CriteriaMap;
BEGIN
v_sql_execute_proc :=
'begin MY_PROCEDURE(:v_date_to_run, :filter_criteria, :o_cursor); end;';
v_cid := DBMS_SQL.open_cursor;
DBMS_SQL.parse (v_cid, v_sql_execute_proc, DBMS_SQL.native);
DBMS_SQL.bind_variable (v_cid, 'v_date_to_run', v_date_to_run);
DBMS_SQL.bind_variable (v_cid, 'filter_criteria', v_filter_criteria);
DBMS_SQL.bind_variable (v_cid, 'o_cursor', o_cursor);
v_dummy := DBMS_SQL.execute (v_cid);
DBMS_SQL.close_cursor (v_cid);
END;
as the result the following error is thrown
Error report:
ORA-06550: line 14, column 3:
PLS-00306: wrong number or types of arguments in call to 'BIND_VARIABLE'
Documentation http://docs.oracle.com/cd/B19306_01/appdev.102/b14258/d_sql.htm says that BIND_VARIABLE takes only a limited number of data types and 'table of varchar' and sys_refcursor are not in the list.
Is there any workaround to pass arguments to a dynamic function which data types are not in the list?
table of varchar is suppoorted through bind_array (though it has to be as per the spec in dbms_sql which is indexed by integer and not varchar2).
can you explain more on why you're using dynamic SQL in this case? as your example does not warrant dynamic SQL as the structure of the call is fixed here.
I know it's old... but if anyone encounters this - it is possible (in 11g, not sure about earlier) to convert the sys ref cursor to a cursor number, bind it, and then transform it to a refcursor after the execute - something like:
declare
vSQL varchar2(1000) := 'declare
v_rc sys_refcursor;
begin
open v_rc for select ''this is a test'' from dual;
:v_cursor_number := dbms_sql.to_cursor_number(v_rc);
end;';
v_rc sys_refcursor;
v_cursor_number NUMBER;
v_cur number;
v_result number;
vFetchValue varchar2(20);
begin
--open the cursor
v_cur := DBMS_SQL.OPEN_CURSOR;
--parse
DBMS_SQL.PARSE(v_cur, vSQL, dbms_sql.native);
--bind the cursor number
DBMS_SQL.BIND_VARIABLE(v_cur, 'v_cursor_number', v_cursor_number);
--execute
v_result := DBMS_SQL.EXECUTE(v_cur);
-- get back the value of the bind cursor number
DBMS_SQL.VARIABLE_VALUE(v_cur,'v_cursor_number', v_cursor_number);
--transform it to a standard sys_refcursor
v_rc := DBMS_SQL.TO_REFCURSOR (v_cursor_number);
--close the cursor
DBMS_SQL.CLOSE_CURSOR(v_cur);
fetch v_rc into vFetchValue;
close v_rc;
dbms_output.put_line(vFetchValue);
end;
/

Returning dataset with PL/SQL and a variable table name

I'm trying to write a PL/SQL function to store a select statement with a variable table name (a bit weird i know but it is actually a good design decision). The following code does not work...but I'm not sure how to both take a variable table name (building the query) and return a dataset. Anyone have any experience in this? TIA.
CREATE OR REPLACE FUNCTION fn_netstat_all (casename in varchar2)
RETURN resultset_subtype
IS
dataset resultset_subtype;
v_sql varchar2(25000);
v_tablename varchar2(50);
begin
v_sql := 'SELECT * FROM ' || casename || '_netstat;';
OPEN dataset FOR
execute immediate v_sql;
return dataset;
end;
If your resultset_subtype is a ref_cursor (or just replace resultset_subtype with a ref_cursor) you could:
CREATE OR REPLACE
FUNCTION fn_netstat_all (
casename IN VARCHAR2
)
RETURN resultset_subtype
IS
dataset resultset_subtype;
BEGIN
OPEN dataset
FOR 'SELECT * FROM ' || casename || '_netstat';
RETURN dataset;
END fn_netstat_all;
FWIW, you might want to look into the DBMS_ASSERT package to wrap the casename variable to help protect against SQL Injection attacks in your dynamic SQL.
Hope it helps...
Below it is assumed all tables are similar. It's also possible to select a subset of colums that are similar in every table without using DBMS_SQL. I have also paid some attention to SQL injection mentioned by Ollie.
create table so9at (
id number(1),
data varchar2(5)
);
insert into so9at values (1, 'A-AAA');
insert into so9at values (2, 'A-BBB');
insert into so9at values (3, 'A-CCC');
create table so9bt (
id number(1),
data varchar2(5)
);
insert into so9bt values (5, 'B-AAA');
insert into so9bt values (6, 'B-BBB');
insert into so9bt values (7, 'B-CCC');
create table secret_identities (
cover_name varchar2(20),
real_name varchar2(20)
);
insert into secret_identities values ('Batman', 'Bruce Wayne');
insert into secret_identities values ('Superman', 'Clark Kent');
/* This is a semi-secure version immune to certain kind of SQL injections. Note
that it can be still used to find information about any table that ends with
't'. */
create or replace function cursor_of (p_table_id in varchar2)
return sys_refcursor as
v_cur sys_refcursor;
v_stmt constant varchar2(32767) := 'select * from ' || dbms_assert.qualified_sql_name(p_table_id || 't');
begin
open v_cur for v_stmt;
return v_cur;
end;
/
show errors
/* This is an unsecure version vulnerable to SQL injection. */
create or replace function vulnerable_cursor_of (p_table_id in varchar2)
return sys_refcursor as
v_cur sys_refcursor;
v_stmt constant varchar2(32767) := 'select * from ' || p_table_id || 't';
begin
open v_cur for v_stmt;
return v_cur;
end;
/
show errors
create or replace procedure print_values_of (p_cur in sys_refcursor) as
type rec_t is record (
id number,
data varchar2(32767)
);
v_rec rec_t;
begin
fetch p_cur into v_rec;
while p_cur%found loop
dbms_output.put_line('id = ' || v_rec.id || ' data = ' || v_rec.data);
fetch p_cur into v_rec;
end loop;
end;
/
show errors
declare
v_cur sys_refcursor;
begin
v_cur := cursor_of('so9a');
print_values_of(v_cur);
close v_cur;
v_cur := cursor_of('so9b');
print_values_of(v_cur);
close v_cur;
/* SQL injection vulnerability */
v_cur := vulnerable_cursor_of('secret_identities --');
dbms_output.put_line('Now we have a cursor that reveals all secret identities. Just see DBMS_SQL.DESCRIBE_COLUMNS ...');
close v_cur;
/* SQL injection made (mostly) harmless - will throw ORA-44004: invalid qualified SQL name */
v_cur := cursor_of('secret_identities --');
close v_cur;
end;
/

Resources