How to get a record using EXECUTE IMMEDIATE? - oracle

I have a bunch of functions with signatures like:
FUNCTION func1 (par1 IN VARCHAR2, par2 IN NUMBER) RETURN my_rec;
and I have a function for retrieving data from this bunch of functions:
FUNCTION get_result (func_name IN VARCHAR2, par1 IN VARCHAR2, par2 IN NUMBER) RETURN my_rec;
IS
rec1 my_rec;
BEGIN
EXECUTE IMMEDIATE 'SELECT ' || func_name || '(:par1, :par2) FROM DUAL'
INTO rec1
USING IN par1, IN par2;
RETURN rec1;
END;
but this code fails with ORA-01007 'variable not in select list'.
How can I rewrite statement?

It all works fine for me (Oracle 10G), one I correct the syntax error in your function definition (the unwanted semi-colon on the first line):
SQL> create type my_rec is object (id integer, name varchar2(30))
2 /
Type created.
SQL> create FUNCTION func1 (par1 IN VARCHAR2, par2 IN NUMBER) RETURN my_rec
2 is
3 l_rec my_rec := my_rec (1, 'x');
4 begin
5 return l_rec;
6 end;
7 /
Function created.
SQL> CREATE OR REPLACE
2 FUNCTION get_result (func_name IN VARCHAR2, par1 IN VARCHAR2, par2 IN NUMBER) RETURN my_rec
3 IS
4 rec1 my_rec;
5 BEGIN
6 EXECUTE IMMEDIATE 'SELECT ' || func_name || '(:par1, :par2) FROM DUAL'
7 INTO rec1
8 USING IN par1, IN par2;
9 RETURN rec1;
10 END;
11 /
Function created.
SQL> select get_result ('func1',1,2) from dual;
GET_RESULT('FUNC1',1,2)(ID, NAME)
-------------------------------------------------
MY_REC(1, 'x')

I think you can concatenate to the query like you did with func_name.

Related

SELECT INTO variable not working in PL\SQL function

Not sure why this isn't working, when the base query (the commented out line) does return a value when I pass in the same id - when executed as a straight query outside the function.
Maybe I need to use execute immediate, but not sure why this is returning a "no data" error. I got it working with execute immediate, but interested to know what the issue is with the syntax below.
create or replace FUNCTION "FN_GET_CSP_TEMPLATE_DETAILS" (
TEMPLATE_USED_ID IN VARCHAR2 DEFAULT NULL
)
RETURN VARCHAR2 AS
v_out VARCHAR2(100);
BEGIN
dbms_output.put_line(TEMPLATE_USED_ID);
SELECT i_chronicle_id INTO v_out from TABLE_A where R_OBJECT_ID = TEMPLATE_USED_ID;
-- // this works // SELECT i_chronicle_id INTO v_out from TABLE_A where R_OBJECT_ID = 'ID_99';
dbms_output.put_line(v_out);
return v_out;
end;
So this returns a count of 1:
select count(*) from TABLE_A WHERE R_OBJECT_ID = 'ID_99';
But this returns a null value, function defined as above:
select FN_GET_CSP_TEMPLATE_DETAILS('ID_99') from dual;
This works fine:
create or replace FUNCTION "FN_GET_CSP_TEMPLATE_DETAILS" (
TEMPLATE_USED_ID IN VARCHAR2 DEFAULT NULL
)
RETURN VARCHAR2 AS
v_out VARCHAR2(100);
sql_stmt VARCHAR2(1000);
BEGIN
dbms_output.put_line(TEMPLATE_USED_ID);
sql_stmt := 'SELECT i_chronicle_id from TABLE_A where R_OBJECT_ID = :a';
EXECUTE IMMEDIATE sql_stmt into v_out using TEMPLATE_USED_ID;
dbms_output.put_line(v_out);
return v_out;
end;
Seems it only doesnt work against this production table. If I recreate a dummy table and a function against the dummy table it works.
CREATE TABLE "ANALYTICS"."TEST_CSP_FUNCTION"
( "R_OBJECT_ID" VARCHAR2(20 BYTE),
"I_CHRONICLE_ID" VARCHAR2(20 BYTE)
)
INSERT INTO TEST_CSP_FUNCTION
(R_OBJECT_ID, I_CHRONICLE_ID)
VALUES
('ID_100', 'Doc A');
INSERT INTO TEST_CSP_FUNCTION
(R_OBJECT_ID, I_CHRONICLE_ID)
VALUES
('ID_101', 'Doc B');
INSERT INTO TEST_CSP_FUNCTION
(R_OBJECT_ID, I_CHRONICLE_ID)
VALUES
('ID_102', 'Doc C');
INSERT INTO TEST_CSP_FUNCTION
(R_OBJECT_ID, I_CHRONICLE_ID)
VALUES
('ID_103', 'Doc D');
create or replace FUNCTION "FN_GET_TEST_CSP_TEMPLATE_DETAILS" (
TEMPLATE_USED_ID IN NVARCHAR2 DEFAULT NULL
)
RETURN VARCHAR2 AS
v_out VARCHAR2(100);
BEGIN
dbms_output.put_line(TEMPLATE_USED_ID);
SELECT I_CHRONICLE_ID INTO v_out FROM TEST_CSP_FUNCTION WHERE R_OBJECT_ID = TEMPLATE_USED_ID;
dbms_output.put_line(v_out);
return v_out;
end;
select FN_GET_TEST_CSP_TEMPLATE_DETAILS(N'ID_103') from dual;
returns:
Doc D
It works if row with appropriate ID exists in the table.
Demo:
SQL> select * From table_a;
R_OBJ I_CHRONICLE_ID
----- --------------
id_99 100
Function:
SQL> create or replace function fn_get_csp_template_details
2 (template_used_id in varchar2 default null)
3 return varchar2 as
4 v_out varchar2(100);
5 begin
6 dbms_output.put_line(template_used_id);
7 select i_chronicle_id
8 into v_out
9 from table_a
10 where r_object_id = template_used_id;
11 dbms_output.put_line(v_out);
12 return v_out;
13 end;
14 /
Function created.
Testing:
SQL> set serveroutput on
SQL> select fn_get_csp_template_details('id_99') as result from dual;
RESULT
--------------------------------------------------------------------------------
100
id_99
100
SQL>
If ID doesn't exist, it'll return NULL (if called from SELECT statement, as in my example):
SQL> select fn_get_csp_template_details('ABC') as result from dual;
RESULT
--------------------------------------------------------------------------------
SQL>
It'll return an exception if called elsewhere:
SQL> declare
2 l_result varchar2(100);
3 begin
4 l_result := fn_get_csp_template_Details('ABC');
5 end;
6 /
declare
*
ERROR at line 1:
ORA-01403: no data found
ORA-06512: at "SCOTT.FN_GET_CSP_TEMPLATE_DETAILS", line 6
ORA-06512: at line 4
SQL>
That's how it goes. So, what did you do to make it NOT work as expected?

Oracle PL/SQL - procedure with array parameter

I need to write an oracle procedure which will have an array of ID's as parameter.
Then I will return a cursor which contains result of select(1).
(1) - select * from table where id in(ID's)
As an option we can pass a string param and then convert string to array.
DECLARE
info sys_refcursor ;
error varchar(255);
BEGIN
package.test_function('1,2,3',info ,error);// info will contain a result cursor for select(1)
END;
Do you have other ideas?
You can create a user-defined collection type:
CREATE TYPE int8_list IS TABLE OF NUMBER(8,0);
Then your package:
CREATE PACKAGE pkg_name AS
PROCEDURE proc_name (
i_ids IN int8_list,
o_cursor OUT SYS_REFCURSOR
);
END;
/
CREATE PACKAGE BODY pkg_name AS
PROCEDURE proc_name (
i_ids IN int8_list,
o_cursor OUT SYS_REFCURSOR
)
IS
BEGIN
OPEN o_cursor FOR
SELECT * FROM table_name WHERE id MEMBER OF i_ids;
END;
END;
/
Then you can call the procedure:
DECLARE
v_info sys_refcursor ;
v_id TABLE_NAME.ID%TYPE;
v_value TABLE_NAME.VALUE%TYPE;
BEGIN
pkg_name.proc_name(int8_list(1,2,3), v_info);
LOOP
FETCH v_info INTO v_id, v_value;
EXIT WHEN v_info%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(v_id || ' ' || v_value);
END LOOP;
END;
/
Which, for the sample data:
CREATE TABLE table_name (id, value) AS
SELECT LEVEL, CHR(64+LEVEL) FROM DUAL CONNECT BY LEVEL <= 5;
Outputs:
1 A
2 B
3 C
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_.

Execute Immediate bind variable

I have the following function that calculates content of the table but when I pass any param it throws:
EXEC DBMS_OUTPUT.PUT_LINE(get_size('employees'))
Error report -
ORA-00903: invalid table name
ORA-06512: at "HR.GET_SIZE", line 5
ORA-06512: at line 1
00903. 00000 - "invalid table name"
Function
CREATE OR REPLACE FUNCTION get_size(v_table_name IN VARCHAR2)
RETURN NUMBER IS total_size NUMBER(16);
plsql_statement VARCHAR2(500) := 'SELECT COUNT(*) FROM :param';
BEGIN
EXECUTE IMMEDIATE plsql_statement INTO total_size USING v_table_name;
RETURN(total_size);
END;
/
EXEC DBMS_OUTPUT.PUT_LINE(get_size('employees'));
You can't bind table names, it needs to be constructed.
CREATE OR REPLACE FUNCTION get_size(v_table_name IN VARCHAR2)
RETURN NUMBER IS total_size NUMBER(16);
plsql_statement VARCHAR2(500)
BEGIN
plsql_statement := 'SELECT COUNT(*) FROM ' || v_table_name;
EXECUTE IMMEDIATE plsql_statement INTO total_size;
RETURN(total_size);
END;
/
EXEC DBMS_OUTPUT.PUT_LINE(get_size('employees'));
You cannot bind table names or column names. You can bind only variables.
In your case, you just need to EXECUTE IMMEDIATE the dynamic SQL.
SQL> CREATE OR REPLACE FUNCTION get_size(v_table_name IN VARCHAR2)
2 RETURN NUMBER IS total_size NUMBER(16);
3 plsql_statement VARCHAR2(500);
4 BEGIN
5 plsql_statement := 'SELECT COUNT(*) FROM ' || v_table_name;
6 EXECUTE IMMEDIATE plsql_statement INTO total_size;
7 RETURN(total_size);
8 END;
9 /
Function created.
SQL>
SQL>
SQL> EXEC DBMS_OUTPUT.PUT_LINE(get_size('EMP'));
14
PL/SQL procedure successfully completed.
SQL>

PL/SQL - execute immediate in pipelined function

I want to execute dynamic query in my pipelined function and return results of this query.
Is it possible to do this?
Pipelined function is convenient for me to achieve good interface for my application cause it behaves like a table.
The function:
CREATE OR REPLACE FUNCTION MyFunction(p_schema VARCHAR2) RETURN MyTableType Pipelined IS
v_query VARCHAR2(1000);
BEGIN
v_query := 'SELECT * FROM TABLE ('||p_schema||'.somepackage.SomeFunction)'; --SomeFunction is another pipelined function
EXECUTE IMMEDIATE v_query;
--Results of the v_query are compatible with MyTableType's row type. But how to return them from pipelined function?
END;
It is possible to combine dynamic SQL and pipelined function but the return type will not be dynamic: the number and type of columns returned will be fixed.
You can use EXECUTE IMMEDIATE with BULK COLLECT (thanks #be here now), dynamic cursors or DBMS_SQL to return more than one row. Here's an example with a dynamic cursor:
SQL> CREATE OR REPLACE PACKAGE pkg AS
2 TYPE test_tab IS TABLE OF test%ROWTYPE;
3 FUNCTION dynamic_cursor(l_where VARCHAR2) RETURN test_tab PIPELINED;
4 END;
5 /
Package created.
SQL> CREATE OR REPLACE PACKAGE BODY pkg IS
2 FUNCTION dynamic_cursor(l_where VARCHAR2) RETURN test_tab PIPELINED IS
3 cc sys_refcursor;
4 l_row test%ROWTYPE;
5 BEGIN
6 OPEN cc FOR 'SELECT * FROM test WHERE ' || l_where;
7 LOOP
8 FETCH cc INTO l_row;
9 EXIT WHEN cc%NOTFOUND;
10 PIPE ROW (l_row);
11 END LOOP;
12 RETURN;
13 END;
14 END;
15 /
Package body created.
Let's call this dynamic function:
SQL> SELECT *
2 FROM TABLE(pkg.dynamic_cursor('id <= 2'));
ID DAT
---------- ---
1 xxx
2 xxx
As always with dynamic SQL, beware of SQL Injection.
I think something like this:
CREATE OR REPLACE FUNCTION MyFunction(par1 VARCHAR2, ...) RETURN MyTableType Pipelined IS
v_query VARCHAR2(1000);
l_result MyTableType;
BEGIN
v_query := --My query created based on parameters
EXECUTE IMMEDIATE v_query into l_result;
pipe row(l_result);
END;
Works only if v_query returns 1 row.
I couldn't get #VincentMalgrat's answer to work. But it was very close. Definitely a big help in the right direction for me.
Here's what I got to work:
Package
CREATE OR REPLACE PACKAGE pkg AS
TYPE test_row IS RECORD ( test_name VARCHAR2 (255), test_number number, test_date date );
TYPE test_tab IS TABLE OF test_row;
FUNCTION dynamic_cursor(l_where VARCHAR2) RETURN test_tab PIPELINED;
END;
Package Body
CREATE OR REPLACE PACKAGE BODY pkg IS
FUNCTION dynamic_cursor(l_where VARCHAR2) RETURN test_tab PIPELINED IS
cc sys_refcursor;
l_row test_row;
BEGIN
OPEN cc FOR 'select name_column, number_column, date_column FROM my_table where number_column ='||l_where;
LOOP
FETCH cc INTO l_row;
EXIT WHEN cc%NOTFOUND;
PIPE ROW (l_row);
END LOOP;
RETURN;
END;
END;

Resources