create or replace FUNCTION macro_If_condition( p_1 varchar2 )
RETURN varchar2 SQL_MACRO is
v_sql varchar2(1000);
BEGIN
if p_1 is not null then
v_sql := q'[ select p_1 from dual ]' ;
else
v_sql := q'[ select 'NULLL' from dual ]' ;
end if;
RETURN v_sql;
END;
SELECT * FROM macro_If_condition( 'input1') t;
Op:
NULLL --- its incorrect bcz we have passed input1 but don’t know why it went to ELSE condition
Can anyone explain why the IF condition is not working. You can try the above sample code.
The value of string parameters in the body of SQL macros is always null. You cannot use them to change the expression you return like this.
If you want to convert null inputs into the string nulll, you need to place this logic in the return string itself. For example, using a case expression like this:
create or replace function macro_if_condition ( p_1 varchar2 )
return varchar2 sql_macro is
v_sql varchar2(1000);
begin
v_sql := q'[
select case when p_1 is not null then p_1 else 'nulll' end p1_val
from dual
]' ;
return v_sql;
end;
/
select * from macro_if_condition ( null );
P1_VA
-----
nulll
select * from macro_if_condition ( 'input' );
P1_VA
-----
input
Related
Is it possible to fetch cursor data into array or table with dynamic cursor parameters ?
Example : We have 3 function fct1(), fct2(), fct3(). They all return a CURSOR with different data (from different table) and their size is between 20 and 100.
I'd like to call this 3 function with a generic function, fill a VARCHAR2 array and for example print this array.
I've found out how to fetch cursor into a VARRAY or VARCHAR2 but then you need to specify which cursor value you add into the array.
CREATE OR REPLACE PACKAGE BODY CURSOR_EXAMPLE AS
FUNCTION fct1 (param01 IN VARCHAR2) RETURN SYS_REFCURSOR AS
my_cursor SYS_REFCURSOR;
BEGIN
OPEN my_cursor FOR
SELECT a, b, c FROM table1, table2, table3;
RETURN my_cursor;
END fct1;
FUNCTION fct2 (param01 IN VARCHAR2) RETURN SYS_REFCURSOR AS
my_cursor SYS_REFCURSOR;
BEGIN
OPEN my_cursor FOR
SELECT q, w, e, r, t, y FROM table4, table5;
RETURN my_cursor;
END fct2;
FUNCTION fct3 (param01 IN VARCHAR2, param02 IN VARCHAR2, param03 IN VARCHAR2) RETURN SYS_REFCURSOR AS
my_cursor SYS_REFCURSOR;
BEGIN
OPEN my_cursor FOR
SELECT x, y, z FROM table6, table7, table8, table8;
RETURN my_cursor;
END fct3;
PROCEDURE generic_function (fct IN NUMBER, param01 IN VARCHAR2, param02 IN VARCHAR2, param03 IN VARCHAR2, cursor_out OUT SYS_REFCURSOR)
AS
BEGIN
IF fct = 1
cursor_out := fct1(param01);
ELSE IF fct = 2
cursor_out := fct2(param01);
ELSE IF
cursor_out := fct3(param01, param02, param03);
END IF;
LOOP
FETCH cursor_out INTO
-- HERE DEPENDING ON fct1, 2 and 3 add cursor_out variable into a VARRAY (extend it as needed)
EXIT WHEN cursor_out%NOTFOUND;
END LOOP;
-- PRINT VARRAY
END generic_function;
END CURSOR_EXAMPLE;
It appears that you are trying to devise a dynamic means of discovering what columns are contained within the cursor.
There is a way, but it involves using DBMS_SQL and once you convert the cursor to a DBMS_SQL cursor, you will have to use DBMS_SQL all the way as you process the records.
Here is an example of a test function that can return one of four different cursors, without static defined types:
CREATE OR REPLACE FUNCTION get_test_cursor (n in number default 1) return SYS_REFCURSOR
IS
c SYS_REFCURSOR;
BEGIN
IF n = 1
THEN
OPEN c FOR SELECT 'A' f1, 'B' f2 FROM DUAL;
END IF;
IF n = 2
THEN
OPEN c FOR SELECT cast(1.234 as number(6,2)) n1, cast(12 as number(8)) n2, 'C' f3 FROM DUAL;
END IF;
IF n = 3
THEN
OPEN c FOR select * from all_objects;
END IF;
if n = 4
then
OPEN c FOR select sysdate as current_dt from dual;
end if;
return c;
END;
/
Now, we can use the DBMS_SQL package to first convert the cursor from a REF CURSOR to a cursor number using dbms_sql.to_cursor_number.
Once we do this, we can use the rest of the DBMS_SQL API to inspect the cursor and do work on its data.
declare
cur sys_refcursor;
c number;
col_cnt INTEGER;
rec_tab DBMS_SQL.DESC_TAB;
--
-- This is a crude helper procedure to display one line of "DESCRIBE" output
--
PROCEDURE print_rec(rec in DBMS_SQL.DESC_REC) IS
BEGIN
DBMS_OUTPUT.PUT_LINE(rpad(rec.col_name, 41) || ' ' ||
rpad(case when rec.col_null_ok then ' ' else 'NOT NULL' end, 8) || ' ' ||
case when rec.col_type = DBMS_TYPES.TYPECODE_VARCHAR or rec.col_type = DBMS_TYPES.TYPECODE_VARCHAR2 then
'VARCHAR2(' || rec.col_max_len || ')'
when rec.col_type = DBMS_TYPES.TYPECODE_CHAR then
'CHAR(' || rec.col_max_len || ')'
when rec.col_type = DBMS_TYPES.TYPECODE_NUMBER then
case when rec.col_precision = 0 and rec.col_scale = -127 then 'NUMBER'
when rec.col_scale = 0 then 'NUMBER('||rec.col_precision||')'
else 'NUMBER(' || rec.col_precision || ', ' || rec.col_scale || ')'
end
when rec.col_type = DBMS_TYPES.TYPECODE_DATE then
'DATE'
else
'UNKNOWN'
end);
END;
begin
cur := test_cursor(3);
--
-- Convert the REF_CUR to a cursor number
--
c := dbms_sql.to_cursor_number(cur);
--
-- Use an API call to describe the columns
--
DBMS_SQL.DESCRIBE_COLUMNS(c, col_cnt, rec_tab);
--
-- Now loop through the columns and show them
--
dbms_output.put_line('Name Null? Type');
dbms_output.put_line('----------------------------------------- -------- ----------------------------');
for j in 1..col_cnt loop
print_rec(rec_tab(j));
end loop;
--
-- We can do other things at this point.
-- When done, close the cursor.
--
DBMS_SQL.CLOSE_CURSOR(c);
end;
/
Here is the output when we pass in 3, which queries the ALL_OBJECTS view:
Name Null? Type
----------------------------------------- -------- ----------------------------
OWNER NOT NULL VARCHAR2(128)
OBJECT_NAME NOT NULL VARCHAR2(128)
SUBOBJECT_NAME VARCHAR2(128)
OBJECT_ID NOT NULL NUMBER
DATA_OBJECT_ID NUMBER
OBJECT_TYPE VARCHAR2(23)
CREATED NOT NULL DATE
LAST_DDL_TIME NOT NULL DATE
TIMESTAMP VARCHAR2(19)
STATUS VARCHAR2(7)
TEMPORARY VARCHAR2(1)
GENERATED VARCHAR2(1)
SECONDARY VARCHAR2(1)
NAMESPACE NOT NULL NUMBER
EDITION_NAME VARCHAR2(128)
SHARING VARCHAR2(13)
EDITIONABLE VARCHAR2(1)
ORACLE_MAINTAINED VARCHAR2(1)
Once you are able to see what the columns of the cursor are, you are on your way to solving the problem of creating a dynamic procedure that consumes random cursors and populates arrays.
I am working on the following stored procedure.
My_Procedure
create or replace PROCEDURE My_Procedure(
v_ID IN NUMBER DEFAULT 0 ,
v_DETAIL_ID IN NUMBER DEFAULT 0 ,
v_HEADER_ID IN NUMBER DEFAULT 0 ,
v_JOB_DETAIL_ID IN NUMBER DEFAULT 0 ,
v_TABLE_NAME IN VARCHAR2,
v_ESCALATION_TYPE IN VARCHAR2 DEFAULT NULL ,
v_COLUMN_LIST IN NVARCHAR2 DEFAULT NULL ,
cv_1 OUT SYS_REFCURSOR,
cv_2 OUT SYS_REFCURSOR ) AS BEGIN NUll; END;
Stored Procedure in which i am executing
DECLARE
v_ident VARCHAR(30000) := NULL;
v_columns
varchar(30000) := NULL;
v_job_header_id number := 0;
BEGIN
SELECT
(
SELECT
rtrim(XMLAGG(xmlelement(e, c.column_name, ',').extract('//text()')
ORDER BY
c.column_id
).getclobval(), ',')
FROM
table1 c
WHERE
c.owner = t.owner
AND c.table_name = t.table_name
)
INTO v_columns
FROM
all_tables t
WHERE
t.table_name = 'TABLE_NAME';
v_ident := 'INSERT INTO TABLE_NAME ('
|| v_columns
|| ') EXEC My_Procedure(0,0,0,''tableName'',null,v_COLUMNS)';
dbms_output.put_line(v_ident);
EXECUTE IMMEDIATE ( v_ident, 'v_JOB_HEADER_ID INT OUT,v_COLUMNS VARCHAR(30000) OUT', v_job_header_id, v_columns );
END;
In this stored procedure i am taking the table columns name and inserting the values according to the columns.
I am getting the error on the execute immediate line. as the declaration of the type of this expression is incomplete or malformed.
I am trying to take a comma delimited string and insert each value as a new row into a table. I have taken the below example from Lalit Kumar B and modified the data to resemble what my data will look like.
DECLARE
L_INPUT VARCHAR2(4000) := '522,33-23,125,658,25,12-500';
L_COUNT BINARY_INTEGER;
L_ARRAY DBMS_UTILITY.LNAME_ARRAY;
BEGIN
DBMS_UTILITY.COMMA_TO_TABLE(LIST => REGEXP_REPLACE(L_INPUT, '(^|,)', '\1x'), TABLEN => L_COUNT, TAB => L_ARRAY);
DBMS_OUTPUT.PUT_LINE(L_COUNT);
FOR I IN 1 .. L_COUNT
LOOP
DBMS_OUTPUT.PUT_LINE('Element ' || TO_CHAR(I) || ' of array contains: ' || SUBSTR(L_ARRAY(I), 2));
INSERT INTO TEST22 VALUES
(SUBSTR(L_ARRAY(I), 2)
);
COMMIT;
END LOOP;
END;
I am receiving the following oracle error: ORA-20001: comma-separated list invalid near 33-23
What can i do to handle data of the form "33-23"? If I take the '-' out of my data the above will run as desired. This is not ideal as some of my data will have '-' in it and it cannot be removed.
One way is to use CONNECT BY to effectively loop through the string elements. If you run just the query you'll see how this works. The regular expression allows for NULL list elements should they occur.
insert into TEST(col_a)
select regexp_substr('522,33-23,125,658,25,12-500', '(.*?)(,|$)', 1, level, null, 1)
from dual
connect by level <= regexp_count('522,33-23,125,658,25,12-500', ',')+1
I had same problem with DBMS_UTILITY.COMMA_TO_TABLE. It has some bugs with numeric strings. I tried some methods and finally write this function instead of it.
CREATE OR REPLACE PACKAGE UTILITY_METHODS IS
TYPE STRING_TAB IS TABLE OF VARCHAR2(512) INDEX BY BINARY_INTEGER;
FUNCTION SPLIT_STR( P_STRING IN VARCHAR2
, P_SEPRATOR_CHAR IN VARCHAR2)
RETURN STRING_TAB;
END UTILITY_METHODS;
CREATE OR REPLACE PACKAGE BODY UTILITY_METHODS IS
FUNCTION SPLIT_STR( P_STRING IN VARCHAR2
, P_SEPRATOR_CHAR IN VARCHAR2)
RETURN STRING_TAB
IS
STR_TAB STRING_TAB;
L_SEP_CHAR VARCHAR2(1) := NVL(P_SEPRATOR_CHAR, ',');
L_PATERN VARCHAR2(10) := '[^' || L_SEP_CHAR || ']+';
BEGIN
IF P_STRING IS NULL THEN
RETURN STR_TAB;
END IF;
FOR RC IN (
WITH L_LINE(STR) AS
(
SELECT P_STRING
FROM DUAL
)
SELECT REGEXP_SUBSTR(STR, L_PATERN, 1, LEVEL) SP_STR
FROM L_LINE
CONNECT BY LEVEL <= REGEXP_COUNT(STR, L_SEP_CHAR) + 1
)
LOOP
STR_TAB(STR_TAB.COUNT) := RC.SP_STR;
END LOOP;
RETURN STR_TAB;
END;
END UTILITY_METHODS;
If you want use this function in a select statement you can change the return type of function to PIPE_LINED.
The cursor name would be passed in as a varchar2, and the cursor itself exists in the same package as the procedure.
Given only the name (and not a cursor reference) is it possible to access the cursor and loop through it?
If this requires the use of an "execute immediate" or similar, that's not out of the question... (though it's not quite clear to me how that would work, I was under the impression anything it declares is out of scope once it completes).
Apologies in advance, this seems like it should be obvious to me but I'm coming up blank.
Thinking about this a bit, I think you're going about it the wrong way. I'd UNION ALL the results from each of the "cursors" together and then use the "cursor name" to eliminate all the unwanted rows (which the optimizer should optimize away) so that you only get the rows you want. So something like
CREATE OR REPLACE PROCEDURE DO_SOMETHING(pin_Cursor_name IN VARCHAR2)
IS
CURSOR csrFruits IS
SELECT UPPER(pin_Cursor_name) AS FRUIT_TYPE,
VARIETY_NAME,
COLOR,
SIZE,
TARTNESS_RATING,
NULL AS FUZZ_LENGTH,
ROOTSTOCK,
NULL AS PEEL_THICKNESS
FROM APPLES
WHERE pin_Cursor_name = 'apples'
UNION ALL
SELECT UPPER(pin_Cursor_name) AS FRUIT_TYPE,
VARIETY_NAME,
COLOR,
SIZE,
NULL AS TARTNESS_RATING,
FUZZ_LENGTH,
NULL AS ROOTSTOCK,
NULL AS PEEL_THICKNESS
FROM PEACHES
WHERE pin_Cursor_name = 'peaches'
UNION ALL
SELECT UPPER(pin_Cursor_name) AS FRUIT_TYPE,
VARIETY_NAME,
COLOR,
SIZE,
NULL AS TARTNESS_RATING,
NULL AS FUZZ_LENGTH,
NULL AS ROOTSTOCK,
PEEL_THICKNESS
FROM KUMQUATS
WHERE pin_Cursor_name = 'kumquats'
UNION ALL
SELECT UPPER(pin_Cursor_name) AS FRUIT_TYPE,
VARIETY_NAME,
'GREEN' AS COLOR,
SIZE,
NULL AS TARTNESS_RATING,
FUZZ_LENGTH,
ROOTSTOCK,
NULL AS PEEL_THICKNESS
FROM KIWIS
WHERE pin_Cursor_name = 'kiwis';
BEGIN
FOR aRow IN csrFruits LOOP
DBMS_OUTPUT.PUT_LINE(pin_Cursor_name || ' - ' ||
aRow.VARIETY_NAME || ', ' ||
aRow.COLOR || ', ' ||
aRow.SIZE);
END LOOP;
END DO_SOMETHING;
So here we have a cursor which will read from one of four different tables (APPLES, PEACHES, KUMQUATS, and KIWIS) depending on the input parameter. The idea is to have each of the subqueries return a rowset of the same "shape", adding NULL AS XXX for each column which an individual subquery doesn't supply.
Best of luck.
Your original problem statement is quite a bit vague and it's unclear for me what constraints you have and what that "other system" expects as a return value. You also might have an XY-problem, so the answer by #bobjarvis might have a valid point too.
The key problem here is that in PL/SQL there is no way to convert an explicit cursor to a cursor variable. Thus the following "simple" solution is not possible:
-- pseudo PL/SQL code
cursor cur_a ...
cursor cur_b ...
function get_cursor(p_cur_name varchar2) return sys_refcursor is
v_cur sys_refcursor;
begin
execute immediate 'open v_cur for p_cur_name';
return v_cur;
end;
v_cur := get_cursor('cur_b');
In the example package below I assume that all the cursors will have the same result set structure. I was lazy and used weak cursor variables even I should have used strong ones. The package code should be easy to follow.
There is at least one other variation that might be useful for you - bulk collect the data to a collection and process the collection with other subroutines. Below print(varchar2) just demonstrates how to open-iterate-close a cursor "dynamically" with a dbms_output.put_line.
create or replace package so48 is
cursor cur_a is
select 'A1' as val, 1 as id from dual union all
select 'A2' as val, 2 as id from dual union all
select 'A3' as val, 3 as id from dual
;
cursor cur_b is
select 'B1' as val, 4 as id from dual union all
select 'B2' as val, 5 as id from dual union all
select 'B3' as val, 6 as id from dual
;
function get_cursor(p_cur_name in varchar2) return sys_refcursor;
procedure print(p_cur in sys_refcursor);
procedure print(p_cur_name in varchar2);
end;
/
show errors
create or replace package body so48 is
function get_cursor(p_cur_name in varchar2) return sys_refcursor is
v_cur sys_refcursor;
begin
case
when p_cur_name = 'A' then
open v_cur for
select 'A1' as val, 1 as id from dual union all
select 'A2' as val, 2 as id from dual union all
select 'A3' as val, 3 as id from dual
;
when p_cur_name = 'B' then
open v_cur for
select 'B1' as val, 4 as id from dual union all
select 'B2' as val, 5 as id from dual union all
select 'B3' as val, 6 as id from dual
;
else
null;
end case;
return v_cur;
end;
procedure print(p_cur in sys_refcursor) is
v_val varchar2(32767);
v_id number;
begin
loop
fetch p_cur into v_val, v_id;
exit when p_cur%notfound;
dbms_output.put_line('(val = ' || v_val || ')(id = ' || v_id || ')');
end loop;
end;
procedure print(p_cur_name in varchar2) is
plsql_compilation_error exception;
pragma exception_init(plsql_compilation_error, -6550);
v_cur_name constant varchar2(32767) := 'so48.' || p_cur_name;
v_plsql constant varchar2(32767) :=
q'[declare
v_val varchar2(32767);
v_id number;
begin
open ]' || v_cur_name || q'[;
loop
fetch ]' || v_cur_name || q'[ into v_val, v_id;
exit when ]' || v_cur_name || q'[%notfound;
dbms_output.put_line('(val = ' || v_val || ')(id = ' || v_id || ')');
end loop;
close ]' || v_cur_name || q'[;
end;]';
begin
execute immediate v_plsql;
exception
when plsql_compilation_error then
dbms_output.put_line('PL/SQL compilation error');
end;
end;
/
show errors
Example run
SQL> exec so48.print(so48.get_cursor('A'))
(val = A1)(id = 1)
(val = A2)(id = 2)
(val = A3)(id = 3)
PL/SQL procedure successfully completed.
SQL> exec so48.print('cur_b')
(val = B1)(id = 4)
(val = B2)(id = 5)
(val = B3)(id = 6)
PL/SQL procedure successfully completed.
SQL>
I have a table and I would like to use a context variable to select from that table.
Table
Key, data
'XX', 'BLAbla'
'yy', 'blaBla'
'zz', 'bLaBla'
'aa', 'lkdjfa'
.....
....
..
My selection is :
select * from Table where key is not in ('XX','zz');
My definition of the context variable is like
Variable := '('||'''xx'''||','||'''yy'''||')';
DBMS__SESSION.SET_CONTEXT('key_context', 'KeyValues', Variable );
Select sys_context('key_context','KeyValues') Result from dual;
Result
('XX','zz')
So I thouhgt this would work:
select * from Table where key is not in sys_context('key_context','KeyValues');
Any suggestions?
What you need is to pass a single string into IN(). You can do it by the following code, borrowed from this AskTom question:
create or replace type myTableType as table of varchar2(100);
/
create or replace function in_list( p_string in varchar2 )
return myTableType
as
l_data myTableType := myTableType();
l_string long default p_string || ',';
l_n number;
begin
loop
exit when l_string is null;
l_data.extend;
l_n := instr( l_string, ',' );
l_data( l_data.count ) := substr( l_string, 1, l_n-1 );
l_string := substr( l_string, l_n+1 );
end loop;
return l_data;
end;
/
select *
from Table
where key is not in (
select * from THE (
select cast(in_list(sys_context('key_context','KeyValues')) as mytableType)
from dual
)
);
But probably it's simplier to keep keys in a table...
See this SO for a similar problem and several solutions:
Oracle Parameters with IN statement?