I am trying to return a list of 3 VARRAYS/COLLECTIONS to my application. I am having trouble though, think I am either implementing the solution incorrectly
create or replace
PROCEDURE "GENERATE_PEOPLE"
(
-- In this example pi_string will be "This.is.a.test"
pi_string IN VARCHAR2 ,
po_firstnames OUT VARRAY ,
po_lastnames OUT VARRAY ,
po_descriptions OUT VARRAY ,
po_error_code OUT VARCHAR2 ,
po_error_message OUT VARCHAR2
)
IS
CURSOR people_cursor IS SELECT firstname, lastname, description FROM people;
BEGIN
FOR person_rec IN people_cursor
LOOP
-- This is where I am trying to return 3 collections of po_firstnames, po_lastnames, po_descriptions
-- The print statements below print out exactly what it is I am trying to return!
dbms_output.put_line('Firstname: ' || person_rec.firstname);
dbms_output.put_line('Lastname: ' || person_rec.lastname);
dbms_output.put_line('Description: ' || pi_string || person_rec.description);
-- This is where the values would be added to the list/array/collection
po_firstnames(num?) := person_rec.firstname;
po_lastnames(num?) := person_rec.lastname;
po_descriptions(num?) := pi_string || person_rec.description;
END LOOP;
RETURN;
END;
Any help is greatly appreciated
Oracle 10g
And I am calling it as such:
DECLARE
TYPE po_firstnames AS vc2_array;
TYPE po_lastnames AS vc2_array;
TYPE po_descriptions AS vc2_array;
po_error_code VARCHAR2(50);
po_error_message VARCHAR2(50);
BEGIN
GENERATE_PEOPLE
(
'This.is.a.test' ,
po_firstnames ,
po_lastnames ,
po_descriptions ,
po_error_code ,
po_error_message
);
END;
VARRAY cannot be used directly as the type of a parameter or variable. Instead you need to create a TYPE that is a VARRAY like this (e.g.):
create type vc2_array as varray(100) of varchar2(4000);
then:
create or replace
PROCEDURE "GENERATE_PEOPLE"
(
-- In this example pi_string will be "This.is.a.test"
pi_string IN VARCHAR2 ,
po_firstnames OUT vc2_array ,
po_lastnames OUT vc2_array ,
po_descriptions OUT vc2_array ,
po_error_code OUT VARCHAR2 ,
po_error_message OUT VARCHAR2
)
Generally I would use TABLE rather than VARRAY, bacause with TABLE you do not have to specify a maximum number of elements:
create type vc2_array as table of varchar2(4000);
The values can then be assigned like this in your loop:
num := num+1; -- num must be declared above and initialised to 0
po_firstnames(num) := person_rec.firstname;
po_lastnames(num) := person_rec.lastname;
po_descriptions(num) := pi_string || person_rec.description;
However it would be more efficient to do this:
create or replace
PROCEDURE "GENERATE_PEOPLE"
(
-- In this example pi_string will be "This.is.a.test"
pi_string IN VARCHAR2 ,
po_firstnames OUT vc2_array ,
po_lastnames OUT vc2_array ,
po_descriptions OUT vc2_array ,
po_error_code OUT VARCHAR2 ,
po_error_message OUT VARCHAR2
)
IS
BEGIN
SELECT firstname, lastname, pi_string||description
BULK COLLECT INTO po_firstnames, po_lastnames, po_descriptions
FROM people;
END;
Related
I would like to archive some situation-based type conversions in PLSQL. I guess, it is just not possible but, let's see: Can I do something like the below:
function my_type( p_type in variant) return varchar2
as
begin
if is_date(p_type) and p_type = trunc(p_type ,'dd')
then
return to_string(p_type,'some_setting');
end if;
-- and so on for numbers and other date-type cases
end;
I am also happy about other ideas. Oracle would convert the data into varchar2 anyways. I am just not happy about how it does it. Are there maybe some flexible data settings that you can recommend?
Best, Peter
ANYDATA
Example:
create table t_anydata (
nsq number(19,0) primary key,
anyd anydata
);
insert into t_anydata(nsq, anyd) values (1, sys.anyData.convertNumber(5) ) ;
insert into t_anydata(nsq, anyd) values (2, sys.anyData.convertDate(to_date('01-10-2019', 'dd-mm-yyyy')) ) ;
insert into t_anydata(nsq, anyd) values (3, sys.anyData.convertVarchar2('test varchar') ) ;
insert into t_anydata(nsq, anyd) values (4, sys.anyData.convertChar('c') ) ;
insert into t_anydata(nsq, anyd) values (5, sys.anyData.convertBDouble(3.14159) ) ;
insert into t_anydata(nsq, anyd) values (6, sys.anyData.ConvertTimestamp(TIMESTAMP '1997-01-31 09:26:50.12') ) ;
insert into t_anydata(nsq, anyd) values (7, sys.anyData.ConvertTimestampTZ(TIMESTAMP '1997-01-31 09:26:50.12') ) ;
insert into t_anydata(nsq, anyd) values (8, sys.anyData.ConvertTimestampLTZ(TIMESTAMP '1997-01-31 09:26:50.12') ) ;
insert into t_anydata(nsq, anyd) values (9, sys.anyData.ConvertCollection( sys.odcivarchar2list( 'abcd', 'efgh' ) ) ) ;
insert into t_anydata(nsq, anyd) values (10, sys.anyData.ConvertCollection( sys.odcinumberlist( 1.1, 2.2, 3.3 ) ) ) ;
commit ;
SELECT nsq,
CASE sys.anyData.gettypename(anyd)
WHEN 'SYS.NUMBER' THEN
TO_CHAR(SYS.ANYDATA.accessNumber(anyd))
WHEN 'SYS.VARCHAR2' THEN
SYS.ANYDATA.accessVarchar2(anyd)
WHEN 'SYS.CHAR' THEN
SYS.ANYDATA.accessChar(anyd)
WHEN 'SYS.DATE' THEN
TO_CHAR(SYS.ANYDATA.accessDate(anyd), 'DD-MON-YYYY HH24:MI:SS')
WHEN 'SYS.TIMESTAMP' THEN
TO_CHAR(SYS.ANYDATA.accessTimestamp(anyd), 'DD-MON-YYYY HH24:MI:SS')
WHEN 'SYS.TIMESTAMP_WITH_TIMEZONE' THEN
TO_CHAR(SYS.ANYDATA.accessTimestampTZ(anyd), 'DD-MON-YYYY HH24:MI:SS')
WHEN 'SYS.TIMESTAMP_WITH_LTZ' THEN
TO_CHAR(SYS.ANYDATA.accessTimestampLTZ(anyd), 'DD-MON-YYYY HH24:MI:SS')
WHEN 'SYS.ODCIVARCHAR2LIST' THEN
'COLLECTION'
END as value,
sys.anyData.gettypename(anyd), vsize(anyd)
FROM t_anydata
;
There are two approaches:
Use if ... then ... else ... statements and perform processing in the branches. It makes the code less readable and maintainable, but it doesn't require extra objects. Then you may use a unified container of the data (for example, anydata).
Use overloading and divide responsibility: perform datatype-dependent processing and serialization in each individual instance of the datatype and then bring the results together. It's possible in Oracle as long as it allows function overloading in packages.
Note that it may cause unexpected results for varchar2 parameter, because it may cause implicit conversion for supported datatypes for which there's no overloaded function exist.
create package pkg_my_to_varchar
as
function f_prepare(p_number in number) return varchar2;
function f_prepare(p_date in date) return varchar2;
function f_prepare(p_timestamp_tz in timestamp with time zone) return varchar2;
function f_prepare(p_varchar in varchar2) return varchar2;
function f_all_together(
p1 varchar2,
p2 varchar2 default null,
p3 varchar2 default null,
p4 varchar2 default null,
p5 varchar2 default null,
p6 varchar2 default null,
p7 varchar2 default null,
p8 varchar2 default null,
p9 varchar2 default null
) return varchar2;
end pkg_my_to_varchar;/
create package body pkg_my_to_varchar
as
function f_prepare(
p_number in number
) return varchar2
as
begin
return 'I''ve processed the number: ' || to_char(p_number, 'RN');
end;
function f_prepare(
p_date in date
) return varchar2
as
begin
return 'I''ve processed the date: ' || to_char(p_date, 'YYYY-MM-DD HH24:MI:SS "and some stuff"');
end;
function f_prepare(
p_timestamp_tz in timestamp with time zone
) return varchar2
as
begin
return 'I''ve processed the timestamp with TZ: ' || to_char(p_timestamp_tz, 'YYYY-MM-DD HH24:MI:SS.FF TZH:TZM');
end;
function f_prepare(
p_varchar in varchar2
) return varchar2
as
begin
return 'I''ve processed the string: ' || p_varchar;
end;
function f_all_together(
p1 varchar2,
p2 varchar2 default null,
p3 varchar2 default null,
p4 varchar2 default null,
p5 varchar2 default null,
p6 varchar2 default null,
p7 varchar2 default null,
p8 varchar2 default null,
p9 varchar2 default null
) return varchar2
as
begin
return p1 || chr(10) || p2 || chr(10) || p3 || chr(10) || p4 || chr(10) || p5 || chr(10) || p6 || chr(10) || p7 || chr(10) || p8 || chr(10) || p9;
end;
end pkg_my_to_varchar;/
with a(n, d, ts, vc) as (
select
123.456,
sysdate,
systimestamp at time zone '+07:30',
'qwerty'
from dual
)
select
a.*
, pkg_my_to_varchar.f_all_together(
pkg_my_to_varchar.f_prepare(n),
pkg_my_to_varchar.f_prepare(d),
pkg_my_to_varchar.f_prepare(ts),
pkg_my_to_varchar.f_prepare(vc)
) as concat_all
from a
N
D
TS
VC
CONCAT_ALL
123.456
16-JAN-23
16-JAN-23 23.13.00.685956 +07:30
qwerty
I've processed the number: CXXIIII've processed the date: 2023-01-16 15:43:00 and some stuffI've processed the timestamp with TZ: 2023-01-16 23:13:00.685956000 +07:30I've processed the string: qwerty
fiddle
If you are trying to pass different data types to a function then it is not possible to have many different versions of a function with different data types; however, you can pass the ANYDATA data type:
CREATE FUNCTION ANYDATA_TO_STRING(
p_anydata IN ANYDATA
) RETURN VARCHAR2
IS
v_typeid PLS_INTEGER;
v_anytype ANYTYPE;
v_result_code PLS_INTEGER;
BEGIN
v_typeid := p_anydata.GetType( typ => v_anytype );
CASE v_typeid
WHEN DBMS_TYPES.TYPECODE_NUMBER THEN
DECLARE
v_value NUMBER;
BEGIN
v_result_code := p_anydata.GetNumber( v_value );
RETURN TO_CHAR( v_value );
END;
WHEN DBMS_TYPES.TYPECODE_VARCHAR2 THEN
DECLARE
v_value VARCHAR2(4000);
BEGIN
v_result_code := p_anydata.GetVarchar2( v_value );
RETURN v_value;
END;
WHEN DBMS_TYPES.TYPECODE_DATE THEN
DECLARE
v_value DATE;
BEGIN
v_result_code := p_anydata.GetDate( v_value );
RETURN TO_CHAR( v_value, 'YYYY-MM-DD HH24:MI:SS' );
END;
END CASE;
RETURN NULL;
END;
/
Then:
WITH table_name (value) AS (
SELECT ANYDATA.ConvertDate(SYSDATE) FROM DUAL UNION ALL
SELECT ANYDATA.ConvertNumber(42) FROM DUAL UNION ALL
SELECT ANYDATA.ConvertVarchar2('Hello') FROM DUAL
)
SELECT ANYDATA_TO_STRING(value)
FROM table_name;
Outputs:
ANYDATA_TO_STRING(VALUE)
2023-01-16 14:48:37
42
Hello
fiddle
I am using Toad For Oracle and I have a procedure in a package where I'm getting the CLOB value for result. I'll be using dummy variables only but I'll be putting the whole process here.
My goal is to a rest web service where my output will be a JSON CLOB.
START OF THE BODY PACKAGE
TYPE in_emp_type IS RECORD ( emp_id NUMBER,
emp_fname VARCHAR2(50), emp_lname VARCHAR2(50),
city VARCHAR2(40), country(50) )
in_emp_rec in_emp_type ;
TYPE out_emp_type IS RECORD (emp_addr_1 VARCHAR2(100), emp_addr_2 VARCHAR2(100);
TYPE out_emp_tab_type IS TABLE OF out_emp_type;
query_tab out_emp_tab_type := out_emp_tab_type ();
PROCEDURE get_employee_details(api_key VARCHAR2, emp_id NUMBER,
emp_fname VARCHAR2, emp_lname VARCHAR2, out_result_json OUT CLOB ) IS
CURSOR get_all_emp IS
select city, country from emp_table
where emp_id = emp_id
and fname = emp_fname
and lname = emp_lname ;
CURSOR get_emp_addr (emp_id NUMBER, city VARCHAR2) IS
select addr_1, addr_2 from emp_addr
where emp_id = emp_id and city = city;
v_city VARCHAR2;
v_country VARCHAR2;
v_json_input CLOB;
v_result_json CLOB;
BEGIN
open get_all_emp;
fetch get_all_emp into v_city , v_country ;
close get_all_emp;
v_json_input := '{"EmployeeDetails":
{"EmployeeID": || emp_id || ' ",
"EmployeeFirstName": || emp_fname || '",
"EmployeeLastName": || emp_lname || '",
"EmployeeCity": || v_city || '",
"EmployeeCountry": || v_country || '"}'
convert_input_json(v_json_input,in_emp_rec);
open get_emp_addr (emp_id, city);
fetch get_emp_addr bulk collect into query_tab ;
v_result_json := convert_result_json(query_tab);
out_result_json := v_result_json;
END;
And this is my sample of the convert_input_json procedures & convert_result_json function
PROCEDURE convert_input_json(input_json IN CLOB, output_json OUT in_emp_type) IS
CURSOR get_details IS
select emp_id, emp_fname, emp_lname, city, country
from json_table(input_json, '$' COLUMNS (
emp_id NUMBER PATH '$.EmployeeDetails.EmployeeID',
emp_fname VARCHAR2(50) PATH '$.EmployeeDetails.EmployeeFirstName',
emp_lname VARCHAR2(50) PATH '$.EmployeeDetails.EmployeeLastName', ... includeother fields here)
BEGIN
output_json := in_emp_type( emp_id => NULL, emp_fname => NULL....)
open get_details;
fetch get_details into output_json.emp_id , output_json.emp_fname ....);
close get_details;
END;
FUNCTION convert_result_json (in_result out_emp_tab_type) RETURN CLOB IS
v_clob CLOB
BEGIN
FOR i in 1...query_tab.count LOOP
v_clob := '{ '||chr(13)||'"customerResults": {'
v_clob := v_clob||chr(13)||chr(9)||chr(9)||'"addr1": "'||query_tab(i).emp_addr_1 ||'",';
v_clob := v_clob||chr(13)||chr(9)||chr(9)||'"addr2": "'||query_tab(i).emp_addr_2 ||'",';
END LOOP;
RETURN (v_clob)
END;
So when I try to run this, I'm getting the ORA-06502: PL/SQL: numeric or value error: character string buffer too small
And I'm not really sure why am I getting this error. Can someone please help me.
There should be an error stack trace showing the line number that raised the exception. Looking at your code, there are two obvious places where this can happen:
The JSON_TABLE COLUMNS mapping - ensure that emp_fname/emp_lname never overflows the allotted 50 characters.
In the convert_result_json function, you are using || to append new addresses to an existing CLOB value. || will convert the operands to varchar2(32767). If the size of the CLOB + the new string being appended > 32KB, it will fail due to the varchar2 datatype limit. If this is your issue, use dbms_lob to append to your LOBs, not ||.
I am trying to split a huge CLOB which contains lines with more than 32K characters.
I tried to use this
SELECT REGEXP_SUBSTR(file_cont, '[^'||chr(10)||']+', 1, LEVEL) AS substr
from data_tab where interface = 'Historical'
CONNECT BY LEVEL <= LENGTH(REGEXP_REPLACE(file_cont, '[^'||chr(10)||']+')) + 1
The table data_tab contains some files with pipe as a separator.
The column file_cont is a clob which contains the file we are interested in.
However, when I try to execute the above query, it looks like there is an infinite loop.
For information, the CLOB contains more than 600 lines.
What I want to do is to split the clob, line by line into distinct CLOB.
Do you know a query that can display this result without falling into an infinite loop?
EDIT : The file's size is 22MB.
Thank you in advance.
I have a special package for split and PCRE regular expressions:
https://github.com/xtender/XT_REGEXP
You can find this function in https://github.com/xtender/XT_REGEXP/blob/master/xt_regexp.pck
/**
* Clob simple split
*/
function clob_split_simple(p_clob in clob,p_delim in varchar2)
return clob_table pipelined is
row clob;
l_b number:=1;
l_e number:=1;
$IF DBMS_DB_VERSION.ver_le_11 $THEN
$ELSE
pragma UDF;
$END
begin
while l_e>0
loop
l_e:=instr(p_clob,p_delim,l_b);
pipe row(substr(p_clob,l_b,case when l_e>0 then l_e-l_b else length(p_clob)+length(p_delim)-l_b end));
l_b:=l_e+length(p_delim);
end loop;
end clob_split_simple;
So you can either use this pipelined function:
select *
from table(xt_regexp.clob_split_simple(:clob,chr(10));
or take this code as an example.
clob_table is just a table of clob:
https://github.com/xtender/XT_REGEXP/blob/master/types.sql
create or replace type clob_table as table of clob;
/
create or replace type date_table as table of date;
/
create or replace type number_table as table of number;
/
create or replace type varchar2_table as table of varchar2(4000);
/
create or replace type xml_table as table of xmltype;
/
Update: fixed a bug with long matches: dbms_lob.substr which returns varchar2, replaced with substr(clob) which return clob.
You can use a PL/SQL function to read the and split the value:
If you have the data type:
CREATE TYPE clob_table AS TABLE OF CLOB;
Then the function:
CREATE FUNCTION split_clob(
p_value IN CLOB,
p_delimiter IN VARCHAR2 DEFAULT ','
) RETURN clob_table PIPELINED
IS
v_start PLS_INTEGER;
v_next PLS_INTEGER;
v_len PLS_INTEGER;
BEGIN
v_start := 1;
LOOP
v_next := DBMS_LOB.INSTR( p_value, p_delimiter, v_start );
v_len := CASE v_next WHEN 0 THEN LENGTH( p_value ) + 1 ELSE v_next END - v_start;
PIPE ROW ( SUBSTR( p_value, v_start, v_len ) );
EXIT WHEN v_next = 0;
v_start := v_next + LENGTH(p_delimiter);
END LOOP;
END;
/
For the sample data:
CREATE TABLE table_name ( value CLOB );
DECLARE
v_value TABLE_NAME.VALUE%TYPE := EMPTY_CLOB();
BEGIN
FOR ch IN 65 .. 68 LOOP
FOR i IN 1 .. 10 LOOP
v_value := v_value || RPAD( CHR(ch), 4000, CHR(ch) );
END LOOP;
IF ch < 68 THEN
v_value := v_value || CHR(10);
END IF;
END LOOP;
INSERT INTO table_name ( value ) VALUES ( v_value );
END;
/
Then the output of:
SELECT SUBSTR( s.column_value, 1, 10 ) AS value,
LENGTH( s.column_value ) AS len
FROM table_name t
CROSS APPLY TABLE( split_clob( t.value, CHR(10) ) ) s
Is:
VALUE
LEN
AAAAAAAAAA
40000
BBBBBBBBBB
40000
CCCCCCCCCC
40000
DDDDDDDDDD
40000
db<>fiddle here
I am trying to use member of in Oracle.
I am able to use this when table type is of number or any other data type. Below is the code for this:
declare
type t is table of number;
lt t;
begin
select channel_key
bulk collect into lt
from dim_channels;
if 22 member of lt then
dbms_output.put_line('ss');
end if;
end;
How do I use member of when the table is based on a record as in the code below.
declare
type rt is record
(
channel_key number(10),
channel_code varchar2(100)
);
type t is table of rt;
lt t;
lrt rt;
begin
select channel_key, channel_code
bulk collect into lt
from dim_channels;
end;
This won't work with plain local PL/SQL record types. To include more attributes you will need an object type with a MAP or ORDER function:
create or replace type demo_ot as object
( channel_key integer
, channel_code varchar2(30)
, map member function demo_map return varchar2 )
/
create or replace type body demo_ot as
map member function demo_map return varchar2
is
begin
return self.channel_key || '<#>' || self.channel_code;
end demo_map;
end;
/
declare
type demo_t is table of demo_ot; -- You would normally create this globally in SQL
my_set demo_t;
my_object demo_ot;
begin
select demo_ot(ckey, ccode)
bulk collect into my_set
from ( select 1 as ckey, 'One' as ccode from dual
union all
select 2 as ckey, 'Two' as ccode from dual );
my_object := demo_ot(2, 'Two');
if my_object member of my_set then
dbms_output.put_line('Member found');
else
dbms_output.put_line('Member not found');
end if;
end;
/
I created below code to test it
create or replace PROCEDURE P_MEMBER_OF_TEST(p_fname IN VARCHAR2,
p_lname in varchar2)
AS
type type_rec is record
(
first_name employees.first_name%type,
last_name employees.last_name%type
);
TYPE T_TAB_TYPE IS TABLE OF type_rec;
T_TAB T_TAB_TYPE;
t_rec type_rec;
i int;
BEGIN
t_rec.first_name := p_fname;
t_rec.last_name := p_lname;
SELECT FIRST_NAME,last_name bulk collect INTO T_TAB FROM
EMPLOYEES;
dbms_output.put_line(t_rec.first_name || ',' || t_rec.last_name);
IF t_rec MEMBER OF T_TAB THEN
DBMS_OUTPUT.PUT_LINE ('YES');
ELSE
DBMS_OUTPUT.PUT_LINE('NO');
END IF;
END;
It compiled with no issues however when i execute it i get error that my connection has been reset , when i comment the if-else-end if block . it gets executed. Can you also suggest what is the problem in code #William Robertson
I have a task that involves updating many triggers which are exactly the same query but applied to several different tables. Is there a way to update all these TRIGGERS using a FOR or similar statement? Actually what I need to do is modify the WHEN clause for all this triggers.
you can use dbms_metadat for this.
for example:
declare
type arr_tab is table of varchar2(30);
v_arr arr_tab;
v_trig clob;
begin
dbms_metadata.set_transform_param( DBMS_METADATA.SESSION_TRANSFORM,
'SQLTERMINATOR', FALSE );
v_arr := arr_tab('TEST_TRIG', 'TEST2_TRIG'); -- change these triggers.
for idx in 1..v_arr.count
loop
v_trig := dbms_metadata.get_ddl('TRIGGER',v_arr(idx), user);
execute immediate regexp_replace(regexp_replace(v_trig, 'ALTER TRIGGER.*', ''), 'WHEN ([^\)]*\))', 'WHEN (1=1)', 1, 1, 'mi');
end loop;
end;
/
the 'WHEN ([^\)]*\))', 'WHEN (1=1)' part replaces the WHEN clause with (in my case) WHEN (1=1).
You can use dba_triggers to extract the text of the trigger into CREATE or replace statements. But due to some of the columns being LONG datatype you will have trouble extracting them as VARCHAR2. This can be resolved by using Tom Kytes package which is lost somewhere on the oracle site. I include my own version which you may have to modify to meet your needs.
Run the select, insert your when clause and then run the create or replace statements.
This won't work due to the trigger_body being a long datatype
select 'CREATE OR REPLACE TRIGGER '|| description
||trigger_body
from dba_triggers
where owner = 'Your schema'
but this should work if your triggers are not more than 4000 characters
select 'CREATE OR REPLACE TRIGGER '|| description
|| ADMIN.LONG_HELP.SUBSTR_OF('select trigger_body from dba_triggers where trigger_name = :0',
1,4000,'0',dt.trigger_name)
from dba_triggers dt
where owner = 'YourSchema';
CREATE OR REPLACE PACKAGE ADMIN.LONG_HELP
/******************************************************************************
NAME: LONG_HELP
PURPOSE: Read fields of type long. (commonly found in data dictionary)
REVISIONS:
Ver Date Author Description
--------- ---------- --------------- ------------------------------------
1.0 10/27/2011 1. Created this package. based on Tom Kyte's column here
http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:839298816582
note that it only retrieves the first 4000 characters of any LONG column
USAGE in a WHERE
INSTR(
ADMIN.LONG_HELP.SUBSTR_OF('SELECT text from all_views where view_name =:o ',
1,4000,'o',m2.obj_name),m1.FK_ID) > 0
******************************************************************************/
--AUTHID CURRENT_USER
--removed to get around ORA-29470: Effective userid or roles are not the same as when cursor was parsed
--restrict usage to admin schema for Oracle 11g
AS
FUNCTION substr_of (p_query IN VARCHAR2,
p_from IN NUMBER,
p_for IN NUMBER,
p_name1 IN VARCHAR2 DEFAULT NULL ,
p_bind1 IN VARCHAR2 DEFAULT NULL ,
p_name2 IN VARCHAR2 DEFAULT NULL ,
p_bind2 IN VARCHAR2 DEFAULT NULL ,
p_name3 IN VARCHAR2 DEFAULT NULL ,
p_bind3 IN VARCHAR2 DEFAULT NULL ,
p_name4 IN VARCHAR2 DEFAULT NULL ,
p_bind4 IN VARCHAR2 DEFAULT NULL )
RETURN VARCHAR2;
END LONG_HELP;
/
CREATE OR REPLACE PACKAGE BODY ADMIN.LONG_HELP
AS
g_cursor NUMBER := DBMS_SQL.open_cursor;
g_query VARCHAR2 (32765);
PROCEDURE bind_variable (p_name IN VARCHAR2, p_value IN VARCHAR2)
IS
BEGIN
IF (p_name IS NOT NULL)
THEN
DBMS_SQL.bind_variable (g_cursor, p_name, p_value);
END IF;
END BIND_VARIABLE;
FUNCTION substr_of (p_query IN VARCHAR2,
p_from IN NUMBER,
p_for IN NUMBER,
p_name1 IN VARCHAR2 DEFAULT NULL ,
p_bind1 IN VARCHAR2 DEFAULT NULL ,
p_name2 IN VARCHAR2 DEFAULT NULL ,
p_bind2 IN VARCHAR2 DEFAULT NULL ,
p_name3 IN VARCHAR2 DEFAULT NULL ,
p_bind3 IN VARCHAR2 DEFAULT NULL ,
p_name4 IN VARCHAR2 DEFAULT NULL ,
p_bind4 IN VARCHAR2 DEFAULT NULL )
RETURN VARCHAR2
AS
/******************************************************************************
NAME: LONG_HELP.SUBSTR_OF
PURPOSE: CONVERT long data fields into VARCHAR2
WHOSE DATA IS CHANGED: none
WHAT USES THIS:
WHERE ARE THE RESOURCES NEEDED:
******************************************************************************/
l_buffer VARCHAR2 (4000);
l_buffer_len NUMBER;
BEGIN
IF (NVL (p_from, 0) <= 0)
THEN
raise_application_error (-20002,
'From must be >= 1 (positive numbers)');
END IF;
IF (NVL (p_for, 0) NOT BETWEEN 1 AND 4000)
THEN
raise_application_error (-20003, 'For must be between 1 and 4000');
END IF;
IF (p_query <> g_query OR g_query IS NULL)
THEN
IF (UPPER (TRIM (NVL (p_query, 'x'))) NOT LIKE 'SELECT%')
THEN
raise_application_error (-20001, 'This must be a select only');
END IF;
DBMS_SQL.parse (g_cursor, p_query, DBMS_SQL.native);
g_query := p_query;
END IF;
bind_variable (p_name1, p_bind1);
bind_variable (p_name2, p_bind2);
bind_variable (p_name3, p_bind3);
bind_variable (p_name4, p_bind4);
DBMS_SQL.define_column_long (g_cursor, 1);
IF (DBMS_SQL.execute_and_fetch (g_cursor) > 0)
THEN
DBMS_SQL.column_value_long (g_cursor,
1,
p_for,
p_from - 1,
l_buffer,
l_buffer_len);
END IF;
RETURN l_buffer;
END substr_of;
END LONG_HELP;
/