While LOOP to stop - oracle

I am trying to figure out how to write this into a WHILE LOOP and stop when it reaches 24.
This what I have so far when I run it it just keeps going forever.
DECLARE
startD DATE := SYSDATE;
monthlyP NUMBER(8,2) := 120;
totalP NUMBER(3) :=24;
dueD DATE;
BEGIN
WHILE totalP <=24 LOOP
totalP := totalP -1;
startD := add_months(startD,1);
dbms_output.put_line('Payment Number' || totalP);
dbms_output.put_line('Date Due' || startD);
dbms_output.put_line('Payment amount $'||to_char(monthlyP,'$9999.99'));
dbms_output.put_line('Donation balance' || totalP );
END LOOP;
END;

You could introduce a "counter" variable, set it to 0, and increase this by 1 at every iteration eg
DECLARE
startD DATE := SYSDATE;
monthlyP NUMBER(8,2) := 120;
totalP constant NUMBER(3) := 24;
counter number := 0 ;
dueD DATE;
BEGIN
WHILE counter < totalP LOOP
counter := counter + 1;
startD := add_months(startD,1);
-- use to_char() - explicit conversion - for every || here ...
dbms_output.put_line('Payment Number ' || to_char( counter ) );
dbms_output.put_line('Date Due ' || to_char( startD, 'YYYY-MM-DD' ) );
dbms_output.put_line('Payment amount $ '||to_char(monthlyP,'$9999.99'));
dbms_output.put_line('Donation balance ' || to_char( totalP ) );
END LOOP;
END;
/
When you have a fixed number of iterations, you can also use a FOR loop, which would make the code more compact (not by much, but still ...)
DECLARE
startD DATE := SYSDATE;
monthlyP NUMBER(8,2) := 120;
totalP constant NUMBER(3) := 24;
dueD DATE;
BEGIN
for counter in 1 .. totalP
loop
startD := add_months(startD,1);
dbms_output.put_line('Payment Number ' || to_char( counter ) );
dbms_output.put_line('Date Due ' || to_char( startD, 'YYYY-MM-DD' ) );
dbms_output.put_line('Payment amount $ '||to_char(monthlyP,'$9999.99'));
dbms_output.put_line('Donation balance ' || to_char( totalP - counter ) );
END LOOP;
END;
/
DBfiddle here

Related

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.

PL/SQL - payment system that dispenses exact change when necessary

I am currently trying to practice PL/SQL with a new procedure called 'Pay' that recognizes when you pay (based on parameter p_Paid) the amount equal to, less than, or greater than what was needed (based on parameter p_Price).
The difference of p_Price and p_Paid is variable v_Return.
When you pay the exact amount where p_Paid = p_Price, (for example, 10=10) then it should say
"No money to return!"
When you pay (p_Paid) less than what was asked (p_Price), then it should show the text
"You're some short!"
And when you pay (p_Paid) more than what was asked (p_Price), then it should give you change using 20, 10, 5, and 1 dollar bills and indicate the precise number of each type of bill, e.g. if p_Paid was 206 and p_Price was 10, then it should show the difference in exact change:
9 $20 bill(s)
1 $10 bill(s)
1 $5 bill(s)
1 $1 bill(s)
This procedure assumes that everything is an integer (no cents involved).
So far I have managed to get the procedure to recognize when the payment was equal to the price, or less than the price, but I am having a horrible time trying to figure out how to get the money return part to work when they pay more than what was asked for. Any help would be GREATLY appreciated; this is what I have so far (apologies in advance on how convoluted it may seem; I just started learning PL/SQL):
CREATE OR REPLACE procedure Pay
(p_Price IN number,
p_Paid IN number)
AS
v_Return number;
v_20 number;
v_10 number;
v_5 number;
v_1 number;
BEGIN
v_Return := (p_Price - p_Paid);
v_20 := 0;
v_10 := 0;
v_5 := 0;
v_1 := 0;
IF v_Return = 0 THEN
dbms_output.put_line('No money to return!');
ELSE
IF p_Price > p_Paid THEN
dbms_output.put_line('You're some short!');
ELSE
IF p_Price < p_Paid THEN
WHILE v_Return > 20 LOOP
v_Return := v_Return - 20;
v_20 := v_20 + 1;
IF v_Return > 10 THEN
v_Return := v_Return - 10;
v_10 := v_10 + 1;
IF v_Return > 5 THEN
v_Return := v_Return - 5;
v_5 := v_5 + 1;
IF v_Return > 1 THEN
v_Return := v_Return - 1;
v_Return := v_1;
dbms_output.put_line(v_20 || '$20 bill(s)');
dbms_output.put_line(v_10 || '$10 bill(s)');
dbms_output.put_line(v_5 || '$5 bill(s)');
dbms_output.put_line(v_1 || '$1 bill(s)');
END IF;
END IF;
END IF;
END LOOP;
END IF;
END IF;
END IF;
END;
/
There're some errors in your code:
You need to escape that single quote in the You're some short! line, but I guess that's a copy-pasting error.
You're calculating v_Return backwards: you're substracting the payment from the price, so v_Return ends up being negative. Try v_Return := (p_Paid - p_Price);
Half your ENDs are wrongly placed, i.e. the END LOOP; line should be after the v_20 := v_20 + 1; line. Same goes for some of the END IF;s.
The last substraction when v_Return is already less than 5 is not necessary. Just assign the remaining amount to v_1.
And finally, though not an error per se, it's not a good idea to mix conditions, like you're doing using v_Return in the first IF but p_Price and p_Paid in the second and third IFs. If you had used v_Return you would have noticed that you were calculating it backwards since it would have printed "You're some short!" when it shouldn't.
EDIT: And now that you mention it, it's true that because you're checking v_Return > x instead of v_Return >= x, when v_Return is exactly x it won't notice that and will return change in lower units (one $5 and five $1 for $10 of change, instead of just one $10). So just use >=.
So your code should be like this (I tested it and it works OK):
CREATE OR REPLACE procedure Pay
(p_Price IN number,
p_Paid IN number)
AS
v_Return number;
v_20 number;
v_10 number;
v_5 number;
v_1 number;
BEGIN
v_Return := (p_Paid - p_Price);
v_20 := 0;
v_10 := 0;
v_5 := 0;
v_1 := 0;
IF v_Return = 0 THEN
dbms_output.put_line('No money to return!');
ELSE
IF v_Return < 0 THEN
dbms_output.put_line('You''re some short!');
ELSE
WHILE v_Return >= 20 LOOP
v_Return := v_Return - 20;
v_20 := v_20 + 1;
END LOOP;
IF v_Return >= 10 THEN
v_Return := v_Return - 10;
v_10 := v_10 + 1;
END IF;
IF v_Return >= 5 THEN
v_Return := v_Return - 5;
v_5 := v_5 + 1;
END IF;
v_1 := v_Return;
dbms_output.put_line(v_20 || '$20 bill(s)');
dbms_output.put_line(v_10 || '$10 bill(s)');
dbms_output.put_line(v_5 || '$5 bill(s)');
dbms_output.put_line(v_1 || '$1 bill(s)');
END IF;
END IF;
END;
/
Just for fun, here's a more structured version. (The money() function that formats currency amounts and the change() procedure would be better as part of a package, but I've nested them inside the pay() procedure for demo convenience.)
create or replace procedure pay
( p_price pls_integer
, p_paid pls_integer )
as
v_remainder pls_integer := p_paid - p_price;
type change_list is table of pls_integer index by pls_integer;
v_change change_list;
function money
( p_amount pls_integer )
return varchar2
is
begin
return to_char(p_amount,'fmL99999');
end money;
procedure change
( p_amount in out nocopy pls_integer
, p_denomination in pls_integer
, p_change in out nocopy change_list )
is
begin
if p_amount >= p_denomination then
p_change(p_denomination) := trunc(p_amount/p_denomination);
p_amount := mod(p_amount,p_denomination);
else
p_change(p_denomination) := 0;
end if;
dbms_output.put_line
( p_change(p_denomination) || ' ' || money(p_denomination) || ' bill' ||
case when p_change(p_denomination) <> 1 then 's' end );
end change;
begin
if p_paid > p_price then
dbms_output.put_line
( 'Price ' || money(p_price) ||
', Paid ' || money(p_paid) || ': Change due ' || money(v_remainder) || ', as:');
change(v_remainder, 20, v_change);
change(v_remainder, 10, v_change);
change(v_remainder, 5, v_change);
change(v_remainder, 1, v_change);
else
dbms_output.put_line
( case
when p_paid = p_price then 'No change due.'
else 'Please enter an additional ' || money(p_price - p_paid)
end );
end if;
end;
Example:
SQL> exec pay(73, 100)
Price $73, Paid $100: Change due $27, as:
1 $20 bill
0 $10 bills
1 $5 bill
2 $1 bills
SQL> exec pay(73, 50)
Please enter an additional $23
Note it becomes much easier to add a $50 bill.
The four calls to change() could be restructured as a loop, but then you'd have to construct the loop and you don't really gain much.
Please try
v_20 := floor(v_return/20);
tmp:= v_return - v_20*20;
v_10 := floor(tmp/10);
tmp:= v_return - v_20*20 - v_10 * 10;
v_5 := floor(tmp/5);
v_1 := tmp - v_5*5;

How to dynamically add interval to timestamp?

I need at some point to increment dynamically a timestamp plsql variable.
So, instead of doing this:
timestamp_ := timestamp_ + INTERVAL '1' DAY;
I would like to do thomething like this:
timestamp_ := timestamp_ + INTERVAL days_ DAY;
It doesn't really work.
My final goal is to dynamically create some scheduler jobs for some entities that have an variable expiration date, to avoid creating a single one which would be often executed.
It sounds like you want
timestamp_ := timestamp + numtodsinterval( days_, 'day' );
I would be somewhat cautious, however, about an architecture that involves creating thousands of scheduler jobs rather than one job that runs periodically to clear out expired rows. A single job is a heck of a lot easier to manage and oversee.
Special note:
1. INTERVAL YEAR TO MONTH and
2. INTERVAL DAY TO SECOND
are the only two valid interval datatypes;
Sample Example:
=============================
DECLARE
l_time INTERVAL YEAR TO MONTH;
l_newtime TIMESTAMP;
l_year PLS_INTEGER := 5;
l_month PLS_INTEGER := 11;
BEGIN
-- Notes :
-- 1. format is using "-" to connect year and month
-- 2. No need to mention any other keyword ; Implicit conversion takes place to set interval
l_time := l_year || '-' || l_month;
DBMS_OUTPUT.put_line ( l_time );
SELECT SYSTIMESTAMP + l_time INTO l_newtime FROM DUAL;
DBMS_OUTPUT.put_line ( 'System Timestamp :' || SYSTIMESTAMP );
DBMS_OUTPUT.put_line ( 'New Timestamp After Addition :' || l_newtime );
END;
=============================
Try this:
DECLARE
l_val NUMBER;
l_result VARCHAR2( 20 );
BEGIN
l_val := 1;
SELECT SYSDATE - INTERVAL '1' DAY * l_val INTO l_result FROM DUAL;
DBMS_OUTPUT.put_line( 'Current Date is ' || SYSDATE || ' minus ' || l_val || ' day(s) is ' || l_result );
END;
Output will be:
Current Date is 25-FEB-16 minus 1 day(s) is 24-FEB-16

utl_raw.cast_to_varchar2 behavior with different character sets

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;
/

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