utl_raw.cast_to_varchar2 behavior with different character sets - oracle

I'm using a pdf package for oracle pl/sql called pl_fpdf to create pdfs on the fly (this is what I have to use at the moment). It works on one database, but doesn't work on the other. I believe I've narrowed down the issue to a difference in character set and the behavior of utl_raw.cast_to_varchar2 when trying to convert image binary to ascii (base64).
The working character set is WE8MSWIN1252, and the other is AL32UTF8 (seems to be much more common these days)
My question is, how do I make utl_raw.cast_to_varchar2 behave the same with AL32UTF8 as it does with WE8MSWIN1252 so that the resulting base64 image data is correct?
Here's the code where I think the issue is. If I'm completely wrong here, then please let me know.
procedure p_putstream(pData in out NOCOPY blob) is
offset integer := 1;
lv_content_length number := dbms_lob.getlength(pdata);
buf_size integer := 2000;
buf raw(2000);
begin
p_out('stream');
-- read the blob and put it in small pieces in a varchar
while offset < lv_content_length loop
dbms_lob.read(pData,buf_size,offset,buf);
p_out(utl_raw.cast_to_varchar2(buf), false);
offset := offset + buf_size;
end loop;
-- put a CRLF at te end of the blob
p_out(chr(10), false);
p_out('endstream');
exception
when others then
error('p_putstream : '||sqlerrm);
end p_putstream;

What is p_out ? A wrapper around dbms_output.put_line ?
Could this be a client character set issue ? According to the utl_raw.cast_to_varchar2 documentation:
"When casting to a VARCHAR2, the current Globalization Support character set is used for the characters within that VARCHAR2."
E.g.
$ export NLS_LANG=AMERICAN_AMERICA.UTF8
$ sqlplus
SQL> select utl_raw.cast_to_varchar2('80') from dual;
UTL_RAW.CAST_TO_VARCHAR2('80')
--------------------------------------------------------------------------------
€
SQL>
But
$ unset NLS_LANG
$ sqlplus
SQL> select utl_raw.cast_to_varchar2('80') from dual;
UTL_RAW.CAST_TO_VARCHAR2('80')
--------------------------------------------------------------------------------
?
SQL>
When database character set is
SQL> select * from nls_database_parameters where parameter like '%CHARACTERSET';
PARAMETER VALUE
------------------------------ ----------------------------------------
NLS_CHARACTERSET WE8MSWIN1252
NLS_NCHAR_CHARACTERSET AL16UTF16

I solved my own issue. As it turns out, the codebase I am using to import DBF files bastardizes the VARCHAR2 datatype as a RAW. I probably should really rewrite it to build the DBF header using RAW operations. That being said, I just hacked it up some more. In particular, I used nchar_cs, a customized version of utl_raw.cast_to_varchar2 and a customized substr (to return nvarchar2)
select ascii(chr(194)) from dual;
select ascii(chr(194 using nchar_cs)) from dual;
select ascii(chr(193)) from dual;
greatly illustrates the root cause (The top case shows as 0 on my XE installation, but 194 on my 11g enterprise setup). I'm really gunshy about posting this code because it is such a hackjob, but it does work now.
create or replace package dbase_fox as
-- procedure to a load a table with records
-- from a DBASE file.
--
-- Uses a BFILE to read binary data and dbms_sql
-- to dynamically insert into any table you
-- have insert on.
--
-- p_dir is the name of an ORACLE Directory Object
-- that was created via the CREATE DIRECTORY
-- command
--
-- p_file is the name of a file in that directory
-- will be the name of the DBASE file
--
-- p_tname is the name of the table to load from
--
-- p_cnames is an optional list of comma separated
-- column names. If not supplied, this pkg
-- assumes the column names in the DBASE file
-- are the same as the column names in the
-- table
--
-- p_show boolean that if TRUE will cause us to just
-- PRINT (and not insert) what we find in the
-- DBASE files (not the data, just the info
-- from the dbase headers....)
procedure load_Table( p_dir in varchar2,
p_file in varchar2,
p_tname in varchar2,
p_cnames in varchar2 default NULL,
p_show in BOOLEAN default FALSE,
p_rownum in BOOLEAN default FALSE);
end;
/
create or replace package body dbase_fox
as
-- Might have to change on your platform!!!
-- Controls the byte order of binary integers read in
-- from the dbase file
BIG_ENDIAN constant boolean default TRUE;
type dbf_header is RECORD
(
version varchar2(25), -- dBASE version number
year int, -- 1 byte int year, add to 1900
month int, -- 1 byte month
day int, -- 1 byte day
no_records int, -- number of records in file,
-- 4 byte int
hdr_len int, -- length of header, 2 byte int
rec_len int, -- number of bytes in record,
-- 2 byte int
no_fields int -- number of fields
);
type field_descriptor is RECORD
(
name varchar2(11),
type char(1),
length int, -- 1 byte length
decimals int -- 1 byte scale
);
type field_descriptor_array
is table of
field_descriptor index by binary_integer;
type rowArray
is table of
varchar2(4000) index by binary_integer;
g_cursor binary_integer default dbms_sql.open_cursor;
function mysubstr(d in varchar2, s in number, l in number) return nvarchar2 is
begin
return substr(d,s,l);
end;
-- Function to convert a binary unsigned integer
-- into a PLSQL number
function to_int( p_data in varchar2 ) return number
is
l_number number default 0;
l_bytes number default length(p_data);
begin
if (big_endian)
then
for i in 1 .. l_bytes loop
l_number := l_number +
ascii(mysubstr(p_data,i,1)) *
power(2,8*(i-1));
end loop;
else
for i in 1 .. l_bytes loop
l_number := l_number +
ascii(mysubstr(p_data,l_bytes-i+1,1)) *
power(2,8*(i-1));
end loop;
end if;
return l_number;
end;
procedure dump( p_data in varchar2 ) is
l_number number default 0;
l_bytes number default length(p_data);
byte_number number;
byte_string nvarchar2 (1);
begin
if( l_bytes > 0 ) then
dbms_output.put_line('toti=' || l_bytes);
for i in 1 .. l_bytes loop
byte_string := substr(p_data,l_bytes-i+1,1);
dbms_output.put_line('i=' || i || ' ref=' || (l_bytes-i+1) || ' val=' || ascii(byte_string));
end loop;
end if;
end;
function mycast( d in varchar2 ) return varchar2 is
--replaces utl_raw.cast_to_varchar2
t varchar2(2000) default '';
l number default length(d)/2;
function h(n in number) return number is
begin
if n > 47 and n < 58 then
return n - 48;
else
return n - 55;
end if;
end;
begin
if( l > 0 ) then
for i in 1 .. l loop
--t := t || substr(d,2*i-1,1) || substr(d,2*i,1);
--dbms_output.put_line('i=' || (2*i-1) || ' val=' || 16*(h(ascii(substr(d,2*i-1,1)))));
--dbms_output.put_line('i=' || (2*i) || ' val=' || (h(ascii(substr(d,2*i,1)))));
--dbms_output.put_line('ii=' || i || ' val=' || ((h(ascii(substr(d,2*i,1))))+16*(h(ascii(substr(d,2*i-1,1))))));
t := t || chr((h(ascii(substr(d,2*i,1))))+16*(h(ascii(substr(d,2*i-1,1)))) using nchar_cs);
end loop;
end if;
return t;
end;
-- Alex from Russia add this function
-- to convert a HexDecimal value
-- into a Decimal value
function Hex2Dec( p_data in varchar2 ) return number
is
l_number number default 0;
l_bytes number default length(p_data);
byte_number number;
byte_string nvarchar2 (1);
begin
if( l_bytes > 0 ) then
for i in 1 .. l_bytes loop
byte_string := substr(p_data,l_bytes-i+1,1);
case byte_string
when 'A' then byte_number:=10;
when 'B' then byte_number:=11;
when 'C' then byte_number:=12;
when 'D' then byte_number:=13;
when 'E' then byte_number:=14;
when 'F' then byte_number:=15;
else byte_number:=to_number(byte_string);
end case;
l_number := l_number + byte_number * power(16,(i-1));
end loop;
return l_number;
else
return 0;
end if;
end;
--Mattia from Italy add this function
function mytrim(p_str in varchar2) return varchar2 is
i number;
j number;
v_res varchar2(100);
begin
for i in 1 .. 11 loop
if ascii(mysubstr(p_str,i,1)) = 0 then
j:= i;
exit;
end if;
end loop;
v_res := mysubstr(p_str,1,j-1);
return v_res;
end mytrim;
-- Routine to parse the DBASE header record, can get
-- all of the details of the contents of a dbase file from
-- this header
procedure get_header
(p_bfile in bfile,
p_bfile_offset in out NUMBER,
p_hdr in out dbf_header,
p_flds in out field_descriptor_array )
is
l_data varchar2(100);
l_hdr_size number default 32;
l_field_desc_size number default 32;
l_flds field_descriptor_array;
begin
p_flds := l_flds;
l_data := mycast(
dbms_lob.substr( p_bfile,
l_hdr_size,
p_bfile_offset ) );
--dump(l_data);
p_bfile_offset := p_bfile_offset + l_hdr_size;
p_hdr.version := ascii( mysubstr( l_data, 1, 1 ) );
p_hdr.year := 1900 + ascii( mysubstr( l_data, 2, 1 ) );
p_hdr.month := ascii( mysubstr( l_data, 3, 1 ) );
p_hdr.day := ascii( mysubstr( l_data, 4, 1 ) );
p_hdr.no_records := to_int( mysubstr( l_data, 5, 4 ) );
--dbms_output.put_line('hdr_len:' || ascii(mysubstr(l_data,9,1)) || ',' || ascii(mysubstr(l_data,10,1)));
p_hdr.hdr_len := to_int( mysubstr( l_data, 9, 2 ) );
p_hdr.rec_len := to_int( mysubstr( l_data, 11, 2 ) );
p_hdr.no_fields := trunc( (p_hdr.hdr_len - l_hdr_size)/
l_field_desc_size );
for i in 1 .. p_hdr.no_fields
loop
l_data := mycast(
dbms_lob.substr( p_bfile,
l_field_desc_size,
p_bfile_offset ));
p_bfile_offset := p_bfile_offset + l_field_desc_size;
p_flds(i).name := mytrim(mysubstr(l_data,1,11));
p_flds(i).type := mysubstr( l_data, 12, 1 );
p_flds(i).length := ascii( mysubstr( l_data, 17, 1 ) );
p_flds(i).decimals := ascii(mysubstr(l_data,18,1) );
end loop;
p_bfile_offset := p_bfile_offset +
mod( p_hdr.hdr_len - l_hdr_size,
l_field_desc_size );
end;
function build_insert
( p_tname in varchar2,
p_cnames in varchar2,
p_flds in field_descriptor_array,
p_rownum in BOOLEAN) return varchar2
is
l_insert_statement long;
begin
l_insert_statement := 'insert into ' || p_tname || '(';
if ( p_cnames is NOT NULL )
then
l_insert_statement := l_insert_statement ||
p_cnames || ') values (';
else
for i in 1 .. p_flds.count
loop
if ( i <> 1 )
then
l_insert_statement := l_insert_statement||',';
end if;
l_insert_statement := l_insert_statement ||
'"'|| p_flds(i).name || '"';
end loop;
--add rownum functionality
if ( p_rownum )
then
l_insert_statement := l_insert_statement ||
',"ROWNUM"';
end if;
l_insert_statement := l_insert_statement ||
') values (';
end if;
for i in 1 .. p_flds.count
loop
if ( i <> 1 )
then
l_insert_statement := l_insert_statement || ',';
end if;
if ( p_flds(i).type = 'D' )
then
l_insert_statement := l_insert_statement ||
'to_date(:bv' || i || ',''yyyymmdd'' )';
else
l_insert_statement := l_insert_statement ||
':bv' || i;
end if;
end loop;
--add rownum functionality
if ( p_rownum )
then
l_insert_statement := l_insert_statement ||
',:bv' || (p_flds.count + 1);
end if;
l_insert_statement := l_insert_statement || ')';
return l_insert_statement;
end;
function get_row
( p_bfile in bfile,
p_bfile_offset in out number,
p_hdr in dbf_header,
p_flds in field_descriptor_array,
f_bfile in bfile,
memo_block in number ) return rowArray
is
l_data varchar2(4000);
l_row rowArray;
l_n number default 2;
f_block number;
begin
l_data := mycast(
dbms_lob.substr( p_bfile,
p_hdr.rec_len,
p_bfile_offset ) );
p_bfile_offset := p_bfile_offset + p_hdr.rec_len;
l_row(0) := mysubstr( l_data, 1, 1 );
for i in 1 .. p_hdr.no_fields loop
l_row(i) := rtrim(ltrim(mysubstr( l_data,
l_n,
p_flds(i).length ) ));
if ( p_flds(i).type = 'F' and l_row(i) = '.' )
then
l_row(i) := NULL;
-------------------working with Memo fields
elsif ( p_flds(i).type = 'M' ) then
--Check is file exists
if( dbms_lob.isopen( f_bfile ) != 0) then
--f_block - memo block length
f_block := Hex2Dec(dbms_lob.substr( f_bfile, 4, to_number(l_row(i))*memo_block+5 ));
--to_number(l_row(i))*memo_block+9 - offset in memo file *.fpt, where l_row(i) - number of
--memo block in fpt file
l_row(i) := mycast(dbms_lob.substr( f_bfile, f_block, to_number(l_row(i))*memo_block+9));
else
dbms_output.put_line('Not found .fpt file');
exit;
end if;
-------------------------------------------
end if;
l_n := l_n + p_flds(i).length;
end loop;
return l_row;
end get_row;
procedure show( p_hdr in dbf_header,
p_flds in field_descriptor_array,
p_tname in varchar2,
p_cnames in varchar2,
p_bfile in bfile,
p_rownum in BOOLEAN )
is
l_sep varchar2(1) default ',';
procedure p(p_str in varchar2)
is
l_str long default p_str;
begin
while( l_str is not null )
loop
dbms_output.put_line( substr(l_str,1,250) );
l_str := substr( l_str, 251 );
end loop;
end;
begin
p( 'Sizeof DBASE File: ' || dbms_lob.getlength(p_bfile) );
p( 'DBASE Header Information: ' );
p( chr(9)||'Version = ' || p_hdr.version );
p( chr(9)||'Year = ' || p_hdr.year );
p( chr(9)||'Month = ' || p_hdr.month );
p( chr(9)||'Day = ' || p_hdr.day );
p( chr(9)||'#Recs = ' || p_hdr.no_records);
p( chr(9)||'Hdr Len = ' || p_hdr.hdr_len );
p( chr(9)||'Rec Len = ' || p_hdr.rec_len );
p( chr(9)||'#Fields = ' || p_hdr.no_fields );
if p_hdr.no_fields > 100 then
return;
end if;
p( chr(10)||'Data Fields:' );
for i in 1 .. p_hdr.no_fields
loop
p( 'Field(' || i || ') '
|| 'Name = "' || p_flds(i).name || '", '
|| 'Type = ' || p_flds(i).Type || ', '
|| 'Len = ' || p_flds(i).length || ', '
|| 'Scale= ' || p_flds(i).decimals );
end loop;
p( chr(10) || 'Insert We would use:' );
p( build_insert( p_tname, p_cnames, p_flds, p_rownum ) );
p( chr(10) || 'Table that could be created to hold data:');
p( 'create table ' || p_tname );
p( '(' );
for i in 1 .. p_hdr.no_fields
loop
--if ( i = p_hdr.no_fields ) then l_sep := ')'; end if;
dbms_output.put
( chr(9) || '"' || p_flds(i).name || '" ');
if ( p_flds(i).type = 'D' ) then
p( 'date' || l_sep );
elsif ( p_flds(i).type = 'F' ) then
p( 'float' || l_sep );
elsif ( p_flds(i).type = 'N' ) then
if ( p_flds(i).decimals > 0 )
then
p( 'number('||p_flds(i).length||','||
p_flds(i).decimals || ')' ||
l_sep );
else
p( 'number('||p_flds(i).length||')'||l_sep );
end if;
elsif ( p_flds(i).type = 'M' ) then
p( 'clob' || l_sep);
else
p( 'varchar2(' || p_flds(i).length || ')'||l_sep);
end if;
end loop;
--add rownum functionality
if ( p_rownum )
then
p( chr(9) || '"ROWNUM" number)' );
end if;
p( '/' );
end;
procedure load_Table( p_dir in varchar2,
p_file in varchar2,
p_tname in varchar2,
p_cnames in varchar2 default NULL,
p_show in BOOLEAN default FALSE,
p_rownum in BOOLEAN default FALSE )
is
l_bfile bfile;
f_bfile bfile;
l_offset number default 1;
l_hdr dbf_header;
l_flds field_descriptor_array;
l_row rowArray;
f_file varchar2(25);
memo_block number;
l_cnt int default 0;
begin
f_file := substr(p_file,1,length(p_file)-4) || '.fpt';
l_bfile := bfilename( p_dir, p_file );
dbms_lob.fileopen( l_bfile );
----------------------- Alex from Russia add this
f_bfile := bfilename( p_dir, f_file );
if( dbms_lob.fileexists(f_bfile) != 0 ) then
dbms_output.put_line(f_file || ' - Open memo file');
dbms_lob.fileopen( f_bfile );
end if;
--------------------------------------------------
get_header( l_bfile, l_offset, l_hdr, l_flds );
if ( p_show )
then
show( l_hdr, l_flds, p_tname, p_cnames, l_bfile, p_rownum );
else
dbms_sql.parse( g_cursor,
build_insert( p_tname, p_cnames, l_flds, p_rownum ),
dbms_sql.native );
-- Memo block size in ftp file
if ( dbms_lob.isopen( f_bfile ) > 0 ) then
memo_block := Hex2Dec(dbms_lob.substr(f_bfile, 2, 7));
else
memo_block := 0;
end if;
for i in 1 .. l_hdr.no_records loop
l_row := get_row( l_bfile,
l_offset,
l_hdr,
l_flds, f_bfile, memo_block );
if ( l_row(0) <> '*' ) -- deleted record
then
for i in 1..l_hdr.no_fields loop
dbms_sql.bind_variable( g_cursor,
':bv'||i,
l_row(i),
4000 );
end loop;
--add rownum functionality
if ( p_rownum )
then
l_cnt := l_cnt + 1;
dbms_sql.bind_variable( g_cursor,
':bv'||(l_hdr.no_fields+1),
l_cnt,
4000 );
end if;
if ( dbms_sql.execute( g_cursor ) <> 1 )
then
raise_application_error( -20001,
'Insert failed ' || sqlerrm );
end if;
end if;
end loop;
end if;
dbms_lob.fileclose( l_bfile );
if ( dbms_lob.isopen( f_bfile ) > 0 ) then
dbms_lob.fileclose( f_bfile );
end if;
--exception
-- when others then
-- if ( dbms_lob.isopen( l_bfile ) > 0 ) then
-- dbms_lob.fileclose( l_bfile );
-- end if;
-- if ( dbms_lob.isopen( f_bfile ) > 0 ) then
-- dbms_lob.fileclose( f_bfile );
-- end if;
-- RAISE;
end;
end;
/

Related

insert or update raw datatype in oracle

I have a source database and I should insert or update some rows in another DB based on my source DB.
My Problem is when updating or inserting raw datatypes, just half of row value comes in target DB.
my raw part of code is:
v_raw RAW(16);
FOR i IN 1 .. v_col_cnt
LOOP
CASE v_desc_tab ( i ).col_type
WHEN 1
THEN
DBMS_SQL.define_column ( v_cursor_id, i, v_namevar, v_desc_tab ( i ).col_max_len );
WHEN 2
THEN
DBMS_SQL.define_column ( v_cursor_id, i, v_numvar );
WHEN 12
THEN
DBMS_SQL.define_column ( v_cursor_id, i, v_datevar );
WHEN 96
THEN
DBMS_SQL.define_column ( v_cursor_id, i, v_charvar, v_desc_tab ( i ).col_max_len );
WHEN 112
THEN
DBMS_SQL.define_column ( v_cursor_id, i, v_clob );
WHEN 23
THEN
DBMS_SQL.define_column ( v_cursor_id, i, v_raw, v_desc_tab ( i ).col_max_len );
ELSE
DBMS_OUTPUT.PUT_LINE('another type');
END CASE;
END LOOP;
WHILE DBMS_SQL.fetch_rows ( v_cursor_id ) > 0
LOOP
v_insert_str := 'INSERT INTO ' || v_table_name || ' VALUES(' ;
v_update_str := 'UPDATE ' || v_table_name || ' SET ';
v_update_where_str := ' WHERE ';
FOR i IN 1 .. v_col_cnt
LOOP
CASE v_desc_tab ( i ).col_type
WHEN 23
THEN
DBMS_SQL.COLUMN_VALUE ( v_cursor_id, i, v_raw );
v_insert_str := v_insert_str || '''' || v_raw || '''' || ', ';
IF is_index(v_table_name, v_desc_tab ( i ).col_name) != 1
THEN
v_update_str := v_update_str || v_desc_tab ( i ).col_name || ' = ''' || v_raw || '''' || ', ';
ELSE
v_update_where_str := v_update_where_str || v_desc_tab ( i ).col_name || ' = ''' || v_raw || '''' || ' and ';
END IF;
END CASE;
END LOOP;
and after creating insert or update command, it will be updated or inserted.
for example a value in my source DB is: 0022F12C24984AECA5E529C533B067B3
but in Target it comes like : 0022F12C24984AEC
I tried RAW(32) and RAW(64) but it didn't work.

Substring CLOB value with value more than 32K

Kindly, I am trying to substring clob value by removing the first 33 characters and the last 2 characters,
I try with the following simple code but it's returned an error: ORA-06502: PL/SQL: numeric or value error
DECLARE
p_Clob_Input CLOB := ''; --> value more than 32K
p_Clob_Output CLOB; --> input CLOB value after removing first 33 characters and last 2 characters
BEGIN
Dbms_Lob.Createtemporary(p_Clob_Output, FALSE);
Dbms_Lob.Writeappend(p_Clob_Output, Dbms_Lob.Getlength(p_Clob_Input)-35,Dbms_Lob.Substr(p_Clob_Input,Dbms_Lob.Getlength(p_Clob_Input)-2, 33));
END;
Then I try with the following code which is working fine, but still, it will fail in case the length is 32001 or 64001, also I am feeling it's too long a code to achieve the objective,
DECLARE
p_Clob_Index NUMBER;
p_Length NUMBER;
p_Chunk VARCHAR2(32000);
p_Clob_Input CLOB := ''; --> value more than 32K
p_Clob_Output CLOB; --> input CLOB value after removing first 33 characters and last 2 characters
BEGIN
Dbms_Lob.Createtemporary(p_Clob_Output, FALSE);
p_Length := Dbms_Lob.Getlength(p_Clob_Input);
p_Clob_Index := 1;
WHILE p_Clob_Index <= p_Length
LOOP
IF p_Clob_Index = 1
THEN
IF p_Length > 32000
THEN
p_Chunk := Dbms_Lob.Substr(p_Clob_Input, 32000, 33);
ELSE
p_Chunk := Dbms_Lob.Substr(p_Clob_Input, p_Length - 2, 33);
END IF;
ELSE
IF p_Clob_Index > p_Length - 32000
THEN
p_Chunk := Dbms_Lob.Substr(p_Clob_Input, (p_Length - p_Clob_Index) - 1, p_Clob_Index);
ELSE
p_Chunk := Dbms_Lob.Substr(p_Clob_Input, 32000, p_Clob_Index);
END IF;
END IF;
IF p_Clob_Index > p_Length - 32000
THEN
p_Clob_Index := p_Length + 1;
ELSE
p_Clob_Index := p_Clob_Index + 32000;
END IF;
Dbms_Lob.Writeappend(p_Clob_Output, Length(p_Chunk), p_Chunk);
END LOOP;
END;
Appreciate your support
My DB Version is 11.2.0.4.0
Thanks ...
Just use SUBSTR as it works with CLOB values longer than 32767 characters:
DECLARE
p_Clob_Input CLOB := EMPTY_CLOB();
p_Clob_Output CLOB;
p_start_offset PLS_INTEGER := 33;
p_end_offset PLS_INTEGER := 2;
BEGIN
FOR i IN 1 .. 10 LOOP
p_clob_input := p_clob_input || LPAD('1234567890', 4000, '1234567890');
END LOOP;
p_clob_output := SUBSTR(
p_clob_input,
p_start_offset + 1,
LENGTH( p_clob_input ) - p_start_offset - p_end_offset
);
DBMS_OUTPUT.PUT_LINE( 'Lengths:' );
DBMS_OUTPUT.PUT_LINE( LENGTH( p_clob_input ) );
DBMS_OUTPUT.PUT_LINE( LENGTH( p_clob_output ) );
DBMS_OUTPUT.PUT_LINE( 'Starts:' );
DBMS_OUTPUT.PUT_LINE( SUBSTR( p_clob_input, 1, 40 ) );
DBMS_OUTPUT.PUT_LINE( LPAD( ' ', p_start_offset, ' ' ) || SUBSTR( p_clob_output, 1, 40 - p_start_offset ) );
DBMS_OUTPUT.PUT_LINE( 'Ends:' );
DBMS_OUTPUT.PUT_LINE( SUBSTR( p_clob_input, 39961 ) );
DBMS_OUTPUT.PUT_LINE( SUBSTR( p_clob_output, 39961 - p_start_offset ) );
END;
/
Which outputs:
Lengths:
40000
39965
Starts:
1234567890123456789012345678901234567890
4567890
Ends:
1234567890123456789012345678901234567890
12345678901234567890123456789012345678
db<>fiddle here
Your first code block is failing with large values because the second argument to writeappend() is varchar2, so is limited to 32k (in a PL/SQL context, 4k from SQL).
You can use the copy procedure instead, which has CLOB arguments:
DBMS_LOB.COPY (
dest_lob IN OUT NOCOPY CLOB CHARACTER SET ANY_CS,
src_lob IN CLOB CHARACTER SET dest_lob%CHARSET,
amount IN INTEGER,
dest_offset IN INTEGER := 1,
src_offset IN INTEGER := 1);
So you can do:
dbms_lob.createtemporary(p_clob_output, false);
dbms_lob.copy(p_clob_output, p_clob_input, dbms_lob.getlength(p_clob_input) - 35, 1, 34);
db<>fiddle demo
Your variable names (and the assignment you hinted at with "value more than 32K", which also won't work for large literal values) suggests you might be working towards a procedure to do this; which you could do as:
create or replace procedure your_proc (
p_clob_input in clob,
p_clob_output out clob,
p_trim_start number,
p_trim_end number
) as
begin
dbms_lob.createtemporary(p_clob_output, false);
dbms_lob.copy(
dest_lob => p_clob_output,
src_lob => p_clob_input,
amount => dbms_lob.getlength(p_clob_input) - (p_trim_start + p_trim_end),
dest_offset => 1,
src_offset => p_trim_start + 1);
end;
/
db<>fiddle
Though it's arguable if that is actually going to make your life much easier than just calling dbms_lob directly.

Bit array in an Oracle column

I have a number of sources for information that I need to store in a table (along with other information). As of now I don't know which and how many sources there will be. The source is not required by business logic but rather is stored only for investigative purposes only. Also, this table will be used in production only once for data migration, so I would like to keep the solution as simple as possible (i.e. not do a properly normalized table structure).
I could create a boolean column for each source (as in source1 char(1) default '0', source2 char(1) default '0' etc.) However, I would have to add a column for each new source. What I'd like to have is a column that's a bit array, each bit representing one source. This is very similar to the order_status column mentioned in the documentation for the BITAND function.
My question is,
What would be the preferred data type for this column assuming that there will be say max. 16 sources? NUMBER(2)?
How would I update this field (e.g. set bit number 3)? I've been looking into UTL_RAW functions but they all seem to (surprise, surprise) expect RAW input which makes things a little cumbersome.
I'm open to other ideas as well, as long adding a new source doesn't require changes in the table structure. (I'm aware that using bit arrays in a database table is rarely a good idea, but these are special circumstances so no need to comment on that.) Our DB is 12c (12.1).
Create an Object Type:
Oracle Setup:
CREATE TYPE bitarray AS OBJECT(
data BLOB,
len NUMBER(38,0),
CONSTRUCTOR FUNCTION bitarray( in_length NUMBER ) RETURN SELF AS RESULT,
CONSTRUCTOR FUNCTION bitarray( in_data VARCHAR2 ) RETURN SELF AS RESULT,
MEMBER FUNCTION getBit( in_index NUMBER ) RETURN NUMBER,
MEMBER FUNCTION setBit( in_index NUMBER, in_value NUMBER ) RETURN bitarray,
MEMBER FUNCTION toString RETURN CLOB,
STATIC FUNCTION byteToRaw( in_value BINARY_INTEGER ) RETURN RAW
);
/
CREATE TYPE BODY bitarray AS
CONSTRUCTOR FUNCTION bitarray( in_length NUMBER ) RETURN SELF AS RESULT
AS
p_raw RAW(1) := BITARRAY.BYTETORAW( 0 );
BEGIN
DBMS_LOB.CREATETEMPORARY( SELF.DATA, FALSE );
SELF.LEN := in_length;
FOR i IN 1 .. CEIL( in_length / 8 ) LOOP
DBMS_LOB.WRITEAPPEND( SELF.DATA, 1, p_raw );
END LOOP;
RETURN;
END;
CONSTRUCTOR FUNCTION bitarray( in_data VARCHAR2 ) RETURN SELF AS RESULT
AS
p_value BINARY_INTEGER := 0;
p_power BINARY_INTEGER := 1;
BEGIN
SELF.LEN := LENGTH( in_data );
DBMS_LOB.CREATETEMPORARY( SELF.DATA, FALSE );
FOR i IN 1 .. SELF.LEN LOOP
IF SUBSTR( in_data, i, 1 ) = '1' THEN
p_value := p_value + p_power;
END IF;
IF MOD( i, 8 ) = 0 OR i = SELF.LEN THEN
DBMS_LOB.WRITEAPPEND( SELF.DATA, 1, BITARRAY.BYTETORAW( p_value ) );
p_value := 0;
p_power := 1;
ELSE
p_power := p_power * 2;
END IF;
END LOOP;
RETURN;
END;
MEMBER FUNCTION getBit( in_index NUMBER ) RETURN NUMBER
AS
p_amount BINARY_INTEGER := 1;
p_raw RAW(1);
p_bit_index BINARY_INTEGER := MOD( in_index - 1, 8 );
p_byte_index BINARY_INTEGER := ( in_index - 1 - p_bit_index ) / 8 + 1;
p_bit_value BINARY_INTEGER := POWER( 2, p_bit_index );
BEGIN
IF in_index IS NULL OR in_index < 1 OR in_index > SELF.LEN THEN
RETURN NULL;
END IF;
DBMS_LOB.READ( SELF.DATA, p_amount, p_byte_index, p_raw );
RETURN BITAND( UTL_RAW.CAST_TO_BINARY_INTEGER( p_raw ), p_bit_value ) / p_bit_value;
END;
MEMBER FUNCTION setBit( in_index NUMBER, in_value NUMBER ) RETURN bitarray
AS
p_amount BINARY_INTEGER := 1;
p_raw RAW(1);
p_bit_index BINARY_INTEGER := MOD( in_index - 1, 8 );
p_byte_index BINARY_INTEGER := ( in_index - 1 - p_bit_index ) / 8 + 1;
p_bit_value RAW(1) := BITARRAY.BYTETORAW( POWER( 2, p_bit_index ) );
p_array bitarray := SELF;
BEGIN
IF in_index IS NULL OR in_value NOT IN ( 0, 1 ) OR in_index < 1 OR in_index > SELF.LEN THEN
RETURN p_array;
END IF;
DBMS_LOB.READ( SELF.DATA, p_amount, p_byte_index, p_raw );
IF in_value = 1 THEN
p_raw := UTL_RAW.BIT_OR( p_raw, p_bit_value );
ELSE
p_raw := UTL_RAW.BIT_AND( p_raw, UTL_RAW.BIT_COMPLEMENT( p_bit_value ) );
END IF;
DBMS_LOB.WRITE( p_array.DATA, p_amount, p_byte_index, p_raw );
RETURN p_array;
END;
MEMBER FUNCTION toString RETURN CLOB
AS
p_string CLOB := EMPTY_CLOB();
BEGIN
FOR i IN 1 .. SELF.LEN LOOP
IF SELF.getBit(i) = 0 THEN
p_string := p_string || '0';
ELSIF SELF.getBit(i) = 1 THEN
p_string := p_string || '1';
ELSE
p_string := p_string || '-';
END IF;
END LOOP;
RETURN p_string;
END;
STATIC FUNCTION byteToRaw( in_value BINARY_INTEGER ) RETURN RAW
AS
BEGIN
RETURN UTL_RAW.SUBSTR( UTL_RAW.CAST_FROM_BINARY_INTEGER( in_value ), 4, 1 );
END;
END;
/
Query:
Then you can use it in SQL:
SELECT BITARRAY(5).toString() AS default_value,
BITARRAY('10110').toString() AS with_values,
BITARRAY('10110').setBit(3,0).toString() AS set_values
FROM DUAL;
Output:
DEFAULT_VALUE | WITH_VALUES | SET_VALUES
:------------ | :---------- | :---------
00000 | 10110 | 10010
Storage in a Table:
CREATE TABLE table_name ( id INT, bits BITARRAY );
INSERT INTO table_name
SELECT 1, bitarray( 4 ).setBit( 1, 1 ).setBit( 4, 1 ) FROM DUAL UNION ALL
SELECT 1, bitarray( '1011001' ) FROM DUAL;
Then query it using:
SELECT id, t.bits.toString() FROM table_name t;
which outputs:
ID | T.BITS.TOSTRING()
-: | :----------------
1 | 1001
1 | 1011001
db<>fiddle here
Instead of creating such a column, how about creating a table? It would contain two columns:
source name
Boolean info
For example:
SQL> create table that_table
2 (source_name varchar2(30),
3 cb_bool number(1) default 0 not null
4 );
Table created.
SQL> insert into that_table
2 select 'source 1', 0 from dual union all
3 select 'source 2', 1 from dual union all
4 select 'source 9', 1 from dual;
3 rows created.
SQL> select * From that_table;
SOURCE_NAME CB_BOOL
------------------------------ ----------
source 1 0
source 2 1
source 9 1
SQL>
As opposed to your idea, this scales and it doesn't really matter how many sources there are - you'd just INSERT a new (or UPDATE existing) row.

Work with associative arrays INDEXED BY VARCHAR2 based on different record types where not all elements may not be present

I am trying to work with with parsed data and place into structures to work with.
I have found a Steve Feuersteins package that takes a string, and given seperator(s) will split it out into associative arrays and nested associative arrays.
With some tweaking I have fit it to some additional things I need. It is initally setup to index the AAs by PLS_INTEGER. In my case the values that I will be parsing will have identifiers for a given field and I thought it would be good to INDEX BY VARCHAR2 and use the field identifers. The downfall I see of indexing VARCHAR2 is that if I reference a index that does not exist, I get a NO_DATA_FOUND exception which I don't want.
The setup of the text string by SEPARATORS is:
GROUP/SEGMENT/FIELD/FIELD/FIELD/SEGMENT/FIELD/FIELD/FIELD/SEGMENT/FIELD/GROUP
So for a given segment there are a definitive potential fields but all may not be present so I cannot guarantee that all indexes that I would potentially try to access would be there.
So the current structure of the collection is:
SUBTYPE maxvarchar2_t IS VARCHAR2 (4000);
TYPE items_tt IS TABLE OF maxvarchar2_t
INDEX BY PLS_INTEGER;
TYPE nested_items_tt IS TABLE OF items_tt
INDEX BY PLS_INTEGER;
TYPE second_nested_items_tt IS TABLE OF nested_items_tt
INDEX BY PLS_INTEGER;
So in the example above
I would declare a second_nested_items_tt and it would be assigned:
1 to many GROUPs
each GROUP would have 1 to many SEGMENTs
each segment would have 1 to many FIELDs
Here is an example:
DECLARE
group_seperator CONSTANT VARCHAR2 (1) := CHR (29);
segment_seperator CONSTANT VARCHAR2 (1) := CHR (30);
field_seperator CONSTANT VARCHAR2 (1) := CHR (28);
loc_string VARCHAR2(4000);
-- field_id VARCHAR2(2);
idx VARCHAR2(2);
TYPE field_id_value_type IS TABLE OF VARCHAR2(100) INDEX BY VARCHAR2(2);
SUBTYPE maxvarchar2_t IS VARCHAR2 (4000);
fields field_id_value_type;
TYPE items_tt IS TABLE OF maxvarchar2_t
INDEX BY PLS_INTEGER;
TYPE nested_items_tt IS TABLE OF items_tt
INDEX BY PLS_INTEGER;
TYPE second_nested_items_tt IS TABLE OF nested_items_tt
INDEX BY PLS_INTEGER;
dataset second_nested_items_tt;
FUNCTION string_to_list (fields_in IN items_tt) --string_in IN VARCHAR2, delim_in IN VARCHAR2)
RETURN field_id_value_type
IS
fields_out_tbl field_id_value_type;
BEGIN
FOR i IN fields_in.FIRST .. fields_in.LAST LOOP
fields_out_tbl(substr(fields_in(i),1,2)) := SUBSTR(fields_in(i),3,LENGTH(fields_in(i)));
END LOOP;
RETURN fields_out_tbl;
END string_to_list;
FUNCTION string_to_list (string_in IN VARCHAR2, delim_in IN VARCHAR2)
RETURN items_tt
IS
c_end_of_list CONSTANT PLS_INTEGER := -99;
l_item maxvarchar2_t;
l_startloc PLS_INTEGER := 1;
items_out items_tt;
PROCEDURE add_item (item_in IN VARCHAR2)
IS
BEGIN
IF item_in = delim_in
THEN
/* We don't put delimiters into the collection. */
NULL;
ELSE
items_out (items_out.COUNT + 1) := item_in;
END IF;
END;
PROCEDURE get_next_item (string_in IN VARCHAR2
, start_location_io IN OUT PLS_INTEGER
, item_out OUT VARCHAR2
)
IS
l_loc PLS_INTEGER;
BEGIN
l_loc := INSTR (string_in, delim_in, start_location_io);
IF l_loc = start_location_io
THEN
/* A null item (two consecutive delimiters) */
item_out := NULL;
ELSIF l_loc = 0
THEN
/* We are at the last item in the list. */
item_out := SUBSTR (string_in, start_location_io);
ELSE
/* Extract the element between the two positions. */
item_out :=
SUBSTR (string_in
, start_location_io
, l_loc - start_location_io
);
END IF;
IF l_loc = 0
THEN
/* If the delimiter was not found, send back indication
that we are at the end of the list. */
start_location_io := c_end_of_list;
ELSE
/* Move the starting point for the INSTR search forward. */
start_location_io := l_loc + 1;
END IF;
END get_next_item;
BEGIN
IF string_in IS NULL OR delim_in IS NULL
THEN
/* Nothing to do except pass back the empty collection. */
NULL;
ELSE
LOOP
get_next_item (string_in, l_startloc, l_item);
IF LENGTH(l_item) != 0 THEN
add_item (l_item);
END IF;
EXIT WHEN l_startloc = c_end_of_list;
END LOOP;
END IF;
RETURN items_out;
END string_to_list;
FUNCTION string_to_list (string_in IN VARCHAR2
, outer_delim_in IN VARCHAR2
, inner_delim_in IN VARCHAR2
)
RETURN nested_items_tt
IS
l_elements items_tt;
l_return nested_items_tt;
BEGIN
/* Separate out the different lists. */
l_elements := string_to_list (string_in, outer_delim_in);
/* For each list, parse out the separate items
and add them to the end of the list of items
for that list. */
FOR indx IN 1 .. l_elements.COUNT
LOOP
l_return (l_return.COUNT + 1) :=
string_to_list (l_elements (indx), inner_delim_in);
END LOOP;
RETURN l_return;
END string_to_list;
FUNCTION string_to_list (string_in IN VARCHAR2
, outer_delim_in IN VARCHAR2
, mid_delim_in IN VARCHAR2
, inner_delim_in IN VARCHAR2
)
RETURN second_nested_items_tt
IS
l_elements items_tt;
l_return second_nested_items_tt;
BEGIN
/* Separate out the different lists. */
/* separated by group (claim) segments */
l_elements := string_to_list (string_in, outer_delim_in);
/* For each list, parse out the separate items
and add them to the end of the list of items
for that list. */
FOR indx IN 1 .. l_elements.COUNT
LOOP
l_return (l_return.COUNT + 1) :=
string_to_list (l_elements (indx), mid_delim_in, inner_delim_in);
END LOOP;
RETURN l_return;
END string_to_list;
BEGIN
loc_string :=
group_seperator
|| -- seg 1
segment_seperator
|| field_seperator
|| 'AB11'
|| field_seperator
|| 'AC1D'
|| field_seperator
|| 'AD2'
|| -- seg2
segment_seperator
|| field_seperator
|| 'AE03'
|| field_seperator
|| 'AF6'
|| -- seg3
segment_seperator
|| field_seperator
|| 'AG30'
|| field_seperator
|| 'AH00'
|| field_seperator
|| 'AIA'
|| field_seperator
|| 'DF1'
|| -- seg4
segment_seperator
|| field_seperator
|| '5T0'
|| field_seperator
|| '772'
|| field_seperator
|| 'RE2'
|| field_seperator
|| 'IU1'
|| -- record 2
group_seperator
|| segment_seperator
|| field_seperator
|| 'EW00'
|| field_seperator
|| 'JH1'
|| field_seperator
|| 'WQA'
|| -- seg2
segment_seperator
|| field_seperator
|| 'RE02'
|| field_seperator
|| 'LL99'
|| field_seperator
|| 'IIKT';
dataset := string_to_list (loc_string , group_seperator, segment_seperator, field_seperator);
DBMS_OUTPUT.put_line( 'List '
|| ' contains '
|| dataset.COUNT
|| ' elements');
FOR outer_index IN 1 .. dataset.COUNT
LOOP
-- DBMS_OUTPUT.put_line( 'List '
-- || outer_index
-- || ' = '
-- || dataset (outer_index) (1) (1) );
-- mid_items := string_to_list(dataset (outer_index), mid_delim_in);
FOR mid_index IN 1 .. dataset(outer_index).COUNT -- mid_items.COUNT
LOOP
FOR inner_index IN 1 ..dataset(outer_index) (mid_index).COUNT
LOOP
DBMS_OUTPUT.put_line('INNER Value is ' || dataset(outer_index) (mid_index) (inner_index));
END LOOP;
fields := string_to_list(dataset(outer_index) (mid_index));
idx := fields.FIRST;
-- DBMS_OUTPUT.PUT_LINE ('Segment: ' || get_description(idx, fields(idx)));
WHILE idx IS NOT NULL
LOOP
DBMS_OUTPUT.PUT_LINE ( idx || ' = ' || fields(idx) );
idx := fields.NEXT(idx);
END LOOP;
END LOOP;
END LOOP;
END;
This example is indexed by PLS_INTEGER except the last loop that uses the VARCHAR2 indexed type.
So is there a way to change these all to VARCHAR2 based indexing and not have to worry about accessing an element that doesn't exist without using the EXISTS function?
My inital thought was I could have a record type for the segments I know like:
segment employee is:
TYPE emp_rec IS RECORD(name VARCHAR2(30),
dob DATE,
position VARCHAR2(30));
but if I come to that segment, how can I determine what values are missing or there for that matter, based on the record above? I don't think this is possible since it is a PL defined record and I cannot gleam any metadata from it, at least to my knowledge.
Basically I am trying to figure out a way to make index by VARCHAR2 work instead of looping through via PLS_INTEGER

Concatenation of CLOB datatypes in a LOOP in PL/SQL

I am trying to concatenate clobs in a PL/SQL loop and it has been returning null whilst when using DBMS_OUTPUT prints out the loop values and when executing each result of the clobs gives an output as well.
The system is meant to execute an already stored SQL in a table based on the report name passed into it. This particular report has many report names; hence the concatenation of each of the reports. The arguments passed are the report name, version of the report you're interested in, the kind of separator you want, and an argument list for the unknowns in the SQL if any. There are also two main types of SQL; 1 that needs the table_name be replaced with a temp table_name and another that needs an ID be appended to a table_name in the SQL.
please find below the code for the REPREF1 function.
CREATE OR REPLACE FUNCTION REPREF1(P_VER IN VARCHAR2 DEFAULT 'LATEST',
P_SEPARATOR IN VARCHAR2 DEFAULT ', ',
P_ARGLIST IN VAR DEFAULT NULL) RETURN CLOB IS
L_CLOB CLOB;
FUNCTION GET_CLOB(P_REPNAM IN VARCHAR2,
P_VER IN VARCHAR2 DEFAULT 'LATEST',
P_SEPARATOR IN VARCHAR2 DEFAULT ', ',
P_ARGLIST IN VAR DEFAULT NULL) RETURN CLOB IS
---------------------------------------------------------------------------------
-- TITLE - GET_CLOB beta - b.0 DATE 2010Mar12
--
-- DESCRIPTION - A function that return a report based on the report name put in
--
-- USAGE - select get_clob(p_repnam,p_ver, p_separator, var(varay(val_1,...val_n), varay(val_1,...val_n))) FROM dual
-----------------------------------------------------------------------------------------------------------------------------
V_SQL VARCHAR2(32767);
L_RESULT CLOB;
V_TITLE VARCHAR2(4000);
V_REPDATE VARCHAR2(30);
V_CNT NUMBER(2);
V_NUMARG NUMBER(3);
V_CDCRU NUMBER(3);
V_BCNT NUMBER(3);
V_NEWTABDAT VARCHAR2(30);
V_NEWTABLIN VARCHAR2(30);
L_COLLIST VARAY;
V_VER VARCHAR2(6);
N PLS_INTEGER;
V_CNTTAB NUMBER(3);
-- EXEC_SQL_CLOB
FUNCTION EXEC_SQL_CLOB(P_SQL IN VARCHAR2,
P_NUMARG IN NUMBER,
P_COLLIST IN VARAY DEFAULT NULL,
P_ARGLIST IN VARAY DEFAULT NULL,
P_SEPARATOR IN VARCHAR2 DEFAULT '') RETURN CLOB IS
------------------------------------------------------------------------------------------------------
-- TITLE - EXEC_SQL_CLOB beta - b.0 DATE 2010Mar22
--
-- DESCRIPTION - A function that returns a clob value after executing the sql query that is passed into it
--
-- USAGE - select exec_sql_clob(p_sql, p_numarg, var(varay(val_1, val_2,...val_n), varay(val_1, val_2,...val_n))) FROM dual
---------------------------------------------------------------------------------------------------------------
L_CUR INTEGER DEFAULT DBMS_SQL.OPEN_CURSOR;
L_STATUS INTEGER;
V_COL VARCHAR2(4000);
L_RESULT CLOB;
L_COLCNT NUMBER DEFAULT 0;
L_SEPARATOR VARCHAR2(10) DEFAULT '';
V_NUMARG NUMBER(3);
BEGIN
-- parse the query for the report
DBMS_SQL.PARSE(L_CUR, P_SQL, DBMS_SQL.NATIVE);
-- whilst it is not more than 255 per line
FOR I IN 1 .. 255
LOOP
BEGIN
-- define each column in the select list
DBMS_SQL.DEFINE_COLUMN(L_CUR, I, V_COL, 2000);
L_COLCNT := I;
EXCEPTION
WHEN OTHERS THEN
IF (SQLCODE = -1007) THEN
EXIT;
ELSE
RAISE;
END IF;
END;
END LOOP;
-- If query has no bind variables
IF (P_ARGLIST IS NULL) THEN
IF (P_NUMARG = 0) THEN
-- Execute the query in the cursor
L_STATUS := DBMS_SQL.EXECUTE(L_CUR);
LOOP
-- Exit loop when fetch is complete
EXIT WHEN(DBMS_SQL.FETCH_ROWS(L_CUR) <= 0);
L_SEPARATOR := '';
FOR I IN 1 .. L_COLCNT
LOOP
DBMS_SQL.COLUMN_VALUE(L_CUR, I, V_COL);
L_RESULT := L_RESULT || L_SEPARATOR || V_COL;
L_RESULT := REPLACE(REPLACE(L_RESULT, CHR(13) || CHR(10), ' '), CHR(10), ' ');
L_SEPARATOR := P_SEPARATOR;
END LOOP;
L_RESULT := L_RESULT || CHR(13);
END LOOP;
ELSE
RAISE_APPLICATION_ERROR(-20011, ' INCORRECT NUMBER OF ARGUMENTS PASSED IN LIST ');
END IF;
-- Query has bind variables
ELSE
-- Check if the numarg passed is the same has stored in the table
SELECT NUMARG
INTO V_NUMARG
FROM REPVER
WHERE REPCODE = P_SQL;
-- If number of arguments is greater than 0
IF (V_NUMARG > 0) THEN
-- Check if the number of arguments are the same
IF (P_NUMARG = V_NUMARG) THEN
-- Replace the bind variables in the query
FOR J IN 1 .. P_ARGLIST.COUNT
LOOP
DBMS_SQL.BIND_VARIABLE(L_CUR, P_COLLIST(J), P_ARGLIST(J));
END LOOP;
-- Execute the query in the cursor
L_STATUS := DBMS_SQL.EXECUTE(L_CUR);
LOOP
-- Exit loop when fetch is complete
EXIT WHEN(DBMS_SQL.FETCH_ROWS(L_CUR) <= 0);
L_SEPARATOR := '';
FOR I IN 1 .. L_COLCNT
LOOP
DBMS_SQL.COLUMN_VALUE(L_CUR, I, V_COL);
L_RESULT := L_RESULT || L_SEPARATOR || V_COL;
L_RESULT := REPLACE(REPLACE(L_RESULT, CHR(13) || CHR(10), ' '), CHR(10), ' ');
L_SEPARATOR := P_SEPARATOR;
END LOOP;
L_RESULT := L_RESULT || CHR(13);
END LOOP;
ELSE
RAISE_APPLICATION_ERROR(-20011, ' INCORRECT NUMBER OF ARGUMENTS PASSED IN LIST ');
END IF;
ELSE
-- If the number of argument is equal to 0
IF (P_NUMARG = 0) THEN
-- Execute the query in the cursor
L_STATUS := DBMS_SQL.EXECUTE(L_CUR);
LOOP
-- Exit loop when fetch is complete
EXIT WHEN(DBMS_SQL.FETCH_ROWS(L_CUR) <= 0);
L_SEPARATOR := '';
FOR I IN 1 .. L_COLCNT
LOOP
DBMS_SQL.COLUMN_VALUE(L_CUR, I, V_COL);
L_RESULT := L_RESULT || L_SEPARATOR || V_COL;
L_RESULT := REPLACE(REPLACE(L_RESULT, CHR(13) || CHR(10), ' '), CHR(10), ' ');
L_SEPARATOR := P_SEPARATOR;
END LOOP;
L_RESULT := L_RESULT || CHR(13);
END LOOP;
ELSE
RAISE_APPLICATION_ERROR(-20011, ' INCORRECT NUMBER OF ARGUMENTS PASSED IN LIST ');
END IF;
END IF;
END IF;
-- Close cursor
DBMS_SQL.CLOSE_CURSOR(L_CUR);
RETURN L_RESULT;
END EXEC_SQL_CLOB;
BEGIN
-- Check if the version entered is null or latest
IF (P_VER IS NULL)
OR (UPPER(P_VER) = UPPER('LATEST')) THEN
SELECT MAX(VER)
INTO V_VER
FROM REPORT B, REPVER R
WHERE UPPER(REPNAM) = UPPER(P_REPNAM)
AND B.REPREF = R.REPREF;
ELSE
V_VER := P_VER;
END IF;
-- Check if the repname and version entered exists
SELECT COUNT(*)
INTO V_CNT
FROM REPORT B, REPVER R
WHERE UPPER(REPNAM) = UPPER(P_REPNAM)
AND VER = V_VER
AND B.REPREF = R.REPREF;
IF (V_CNT > 0) THEN
-- Store the SQL statement, title and number of arguments of the report name passed.
SELECT REPCODE, REPTITLE, NUMARG, COLLIST
INTO V_SQL, V_TITLE, V_NUMARG, L_COLLIST
FROM REPVER R, REPORT B
WHERE UPPER(REPNAM) = UPPER(P_REPNAM)
AND B.REPREF = R.REPREF
AND VER = V_VER;
V_REPDATE := TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI');
L_RESULT := V_TITLE || ' (' || P_REPNAM || ' version ' || V_VER || ') generated ' || V_REPDATE || CHR(13) || CHR(13);
-- Check for some specific type of queries
SELECT COUNT(*)
INTO V_CDCRU
FROM REPVER R, REPORT B
WHERE CTDDATA = 'Y'
AND UPPER(REPNAM) = UPPER(P_REPNAM)
AND B.REPREF = R.REPREF
AND VER = V_VER;
SELECT COUNT(*)
INTO V_BCNT
FROM REPVER R, BODCREPS B
WHERE BENLIST = 'Y'
AND UPPER(REPNAM) = UPPER(P_REPNAM)
AND B.REPREF = R.REPREF
AND VER = V_VER;
IF (V_CDCRU > 0) THEN
V_NEWTABDATA := 'CT_' || 'DAT_' || P_ARGLIST(1) (P_ARGLIST(1).FIRST);
V_NEWTABLINK := 'CT_' || 'LIN_' || P_ARGLIST(1) (P_ARGLIST(1).FIRST);
-- Check if the tables exist
SELECT COUNT(*)
INTO V_CNTTAB
FROM ALL_TABLES
WHERE TABLE_NAME = V_NEWTABDAT
OR TABLE_NAME = V_NEWTABLIN
AND OWNER = 'SCOTT';
IF (V_CNTTAB > 0) THEN
V_SQL := UPPER(V_SQL);
V_SQL := REPLACE(V_SQL, 'CT_DAT_CRU', V_NEWTABDAT);
V_SQL := REPLACE(V_SQL, 'CT_LIN_CRU', V_NEWTABLIN);
ELSE
V_SQL := 'SELECT ''THE TABLE NOT CREATED YET''
FROM DUAL';
END IF;
END IF;
IF (V_BCNT > 0) THEN
V_SQL := UPPER(V_SQL);
V_SQL := REPLACE(V_SQL, 'LIST', P_ARGLIST(1) (P_ARGLIST(1).LAST));
END IF;
IF (P_ARGLIST IS NULL) THEN
-- execute the query
L_RESULT := L_RESULT || EXEC_SQL_CLOB(V_SQL, V_NUMARG, L_COLLIST, NULL, P_SEPARATOR);
ELSE
N := P_ARGLIST.COUNT;
-- execute the query
L_RESULT := L_RESULT || EXEC_SQL_CLOB(V_SQL, V_NUMARG, L_COLLIST, P_ARGLIST(N), P_SEPARATOR);
END IF;
RETURN L_RESULT;
ELSE
RAISE_APPLICATION_ERROR(-20012, P_REPNAM || ' or ' || P_VER || ' DOES NOT EXIST ');
END IF;
END GET_CLOB;
BEGIN
FOR I IN (SELECT REPNAM
FROM REPORT
WHERE REPREF NOT IN ('R01', 'R02', 'R03', 'R04'))
LOOP
SELECT CONCAT_CLOB(GET_CLOB(I.REPNAM, P_VER, P_SEPARATOR, P_ARGLIST))
INTO L_CLOB
FROM DUAL;
DBMS_OUTPUT.PUT_LINE(I.REPNAM);
-- DBMS_OUTPUT.PUT_LINE (COUNT(i.REPNAM));
END LOOP;
RETURN L_CLOB;
END REPREF1;
/
Cheers,
Tunde
Many thanks APC for making the code look better.
#Robert, the last loop in the code returns null even with the CONCAT_CLOB aggregate function that concatenates clobs.
FOR I IN (SELECT REPNAM
FROM REPORT
WHERE REPREF NOT IN ('R01', 'R02', 'R03', 'R04'))
LOOP
SELECT CONCAT_CLOB(GET_CLOB(I.REPNAM, P_VER, P_SEPARATOR, P_ARGLIST))
INTO L_CLOB
FROM DUAL;
DBMS_OUTPUT.PUT_LINE(I.REPNAM);
END LOOP;
when I try this,
FOR I IN (SELECT REPNAM
FROM REPORT
WHERE REPREF NOT IN ('R01', 'R02', 'R03', 'R04'))
LOOP
L_CLOB := L_CLOB || CHR(13) || GET_CLOB(I.REPNAM, P_VER, P_SEPARATOR, P_ARGLIST);
DBMS_OUTPUT.PUT_LINE(I.REPNAM);
END LOOP;
It also gives null; but this time the dbms output for the repnam are not complete.
Don't know about your code. Here is how it works for me:
Whenever I create a function returning a clob value I do this:
function foo return clob is
l_clob clob;
begin
dbms_lob.createtemporary(lob_loc => l_clob, cache => true, dur => dbms_lob.call);
...
return l_clob;
end;
When concatenating values into a clob I use a function:
procedure add_string_to_clob(p_lob in out nocopy clob
,p_string varchar2) is
begin
dbms_lob.writeappend(lob_loc => p_lob, amount => length(p_string), buffer => p_string);
end;
You have to use
dbms_lob.substr(your clob parameter,start position, length)
e.g
dbms_output('my clob value:' || dbms_lob.substr(your clob parameter,start position, length);
But you can print in a string max 4000 character, you can then use this in a looping function to print 4000 characters in each line.

Resources