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

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;

Related

oracle sequence match & exact word match for a column

currently matching with UTL_MATCH.jaro_winkler_similarity (upper(colname),upper(variablename)) jws.
how to achieve sequence match & exact word match?
That's exact match, then. Is it not? Then you just have to compare colname to variablename, directly, e.g.
if colname = variablename then
-- that's exact match, do something
create or replace FUNCTION NUMOFSEQWORDS
(
P_STR1 IN VARCHAR2
, P_STR2 IN VARCHAR2
) RETURN NUMBER AS
l_str1 varchar2(4000) := p_str1;
l_str2 varchar2(4000) := p_str2;
l_res number default 0;
l_del_pos1 number;
l_del_pos2 number;
l_word1 varchar2(1000);
l_word2 varchar2(1000);
l_word_cnt number;
l_word_cnt1 number;
l_word_cnt2 number;
l_word_weight number:=0;
l_jws number:=0;
BEGIN
select regexp_count(l_str1, '[^ ]+') into l_word_cnt1 from dual;
select regexp_count(l_str2, '[^ ]+') into l_word_cnt2 from dual;
l_word_cnt:=LEAST(l_word_cnt1,l_word_cnt2);
--l_word_cnt:=greatest(l_word_cnt1,l_word_cnt2);
l_word_weight:=100.0/l_word_cnt;
begin
loop
l_del_pos1 := instr(l_str1, ' ');
l_del_pos2 := instr(l_str2, ' ');
case l_del_pos1
when 0
then l_word1 := l_str1;
l_str1 := '';
else l_word1 := substr(l_str1, 1, l_del_pos1 - 1);
end case;
case l_del_pos2
when 0
then l_word2 := l_str2;
l_str2 := '';
else l_word2 := substr(l_str2, 1, l_del_pos2 - 1);
end case;
exit when (l_word1 <> l_word2) or
((l_word1 is null) or (l_word2 is null));
if (l_word1 = l_word2) then
l_res := l_res + 1;
end if;
l_str1 := substr(l_str1, l_del_pos1 + 1);
l_str2 := substr(l_str2, l_del_pos2 + 1);
end loop;
--return l_res;
return l_res*l_word_weight;
end;
END NUMOFSEQWORDS;
I ended up creating function above to find the match weightage between two strings
We can use LEAST or greatest to find weightage

How can I do a procedure that tells me the size of the substrings?

I am learning plsql and I have some doubts.
How do I get the size of all substrings in the string? The way I did it, I'm only managing to return the size of just the first substring.
set serveroutput on;
create or replace procedure pr_size (value varchar2)
as
answer varchar2 (300): = '';
begin
for x in 1 .. length (value) loop
cont: = x;
if substr (value, x, 1) = ''
then dbms_output.put_line ('Size:' || length (answer));
exit;
else answer: = answer || substr (value, x, 1);
end if;
end loop;
end;
call pr_size ('Word size calculation test');
you can do something like this
DECLARE
type string_table is table of VARCHAR2(100);
st string_table;
BEGIN
SELECT regexp_substr('THIS IS TEST', '[^ ]+', 1, LEVEL) BULK COLLECT INTO st FROM dual CONNECT by level <= regexp_count('THIS IS TEST', '[^ ]+');
for i in 1..st.COUNT
LOOP
dbms_output.put_line(length(st(i)));
END LOOP;
END;
If you want a string of length like in your code then
CREATE OR REPLACE PROCEDURE pr_size (value varchar2)
AS
type string_table is table of VARCHAR2(100);
st string_table;
answer VARCHAR2(100);
BEGIN
SELECT regexp_substr(value, '[^ ]+', 1, LEVEL) BULK COLLECT INTO st FROM dual CONNECT by level <= regexp_count(value, '[^ ]+');
for i in 1..st.COUNT
LOOP
answer := length(st(i))||' '||answer;
END LOOP;
dbms_output.put_line(answer);
END;
If you want to use your logic then I have corrected your logic
DECLARE
value VARCHAR2(100) := 'this is test for word length';
answer VARCHAR2(100);
cont PLS_INTEGER := 0;
BEGIN
for x in 1 .. length (value) loop
if substr (value, x, 1) = ' ' then
dbms_output.put_line ('Size:' || cont);
cont := 0;
ELSE
cont := cont + 1;
end if;
end loop;
dbms_output.put_line ('Size:' || cont);
END;

Printing Non Repeating Characters first

I am new to PL/SQL and I am trying to write a procedure which would print the non repeating characters of an input string first and Repeating characters of the string in the last. For example if the input string is "Array" then the output should be "yArra".
I wrote a part of it for searching the no. of occurrences of a repeating character, but don't know how exactly should it be printed at the first place.
I wrote an algorithm on this on how can this be made to work but finding difficult to code
Thanks in advance for your help!
I am trying to write a procedure which would print the non repeating
characters of an input string first and Repeating characters of the
string in the last.
You can do this using a pure PLSQL code as below:
create or replace procedure prnt_letter(strng varchar2) as
var varchar2(1);
var1 varchar2(1000) := '';
var2 varchar2(1);
var3 varchar2(1000) := '';
strn_len number;
begin
dbms_output.put_line('Input String --> ' || strng);
strn_len := length(strng);
var := substr(strng, 1, 1);
for i in 1 .. strn_len loop
if (var = substr(strng, i, 1)) then
var2 := substr(strng, i, 1);
var3 := var3 || var2;
var := substr(strng, i, 1);
else
var1 := var1 || substr(strng, i, 1);
var := substr(strng, i, 1);
end if;
end loop;
dbms_output.put_line('Output String --> '||var1 || var3);
end;
EDIT:
Here is my revised solution both in PLSQL and SQL. This works for any string.
PLSQL:
create or replace procedure prnt_letter(strng varchar2) as
var1 varchar2(1000) := '';
strn_len number;
begin
dbms_output.put_line('Input String --> ' || strng);
strn_len := length(strng);
SELECT reverse (LISTAGG (vertical, '') WITHIN GROUP (ORDER BY 1 DESC))
into var1
FROM (
SELECT SUBSTR (strng, LEVEL, 1) Vertical
FROM DUAL
CONNECT BY LEVEL <= strn_len
) ;
dbms_output.put_line('Output String --> '||var1 );
end;
Output:
SQL> execute prnt_letter('rajjjjkkmmaaljjjl');
Input String --> rajjjjkkmmaaljjjl
Output String --> rmmllkkjjjjjjjaaa
PL/SQL procedure successfully completed.
SQL> execute prnt_letter('bubble');
Input String --> bubble
Output String --> ulebbb
PL/SQL procedure successfully completed.
SQL:
-- Logic used:
1) The input string is first arranged vertically in separate rows and
then ordered
2) Using LISTAGG, the result was assembled as a single ordered string
3) Using REVERSE the non-repeating string is brought to the starting
of the string.
SELECT reverse (LISTAGG (vertical, '') WITHIN GROUP (ORDER BY 1 DESC)) col1
FROM ( SELECT SUBSTR ('rajjjjkkmmaaljjjl', LEVEL, 1) Vertical
FROM DUAL
CONNECT BY LEVEL <= LENGTH ('rajjjjkkmmaaljjjl')
)
Try and use the REGEXP_COUNT function for the same. You can first provide a filter where this result >1 to find repeating characters and then concatenate them with the ones whose count = 1.
Check how to use regexp_count
I think the solution can be acheived by just using pure SQL rather than using PLSQL. Hope below snippet helps.
SELECT a.COL
||REPLACE('&Enter_text',a.col,'') output
FROM
(SELECT regexp_count('&Enter_text',SUBSTR('&Enter_text',level,1)) col1,
SUBSTR('&Enter_text',level,1) col
FROM DUAL
CONNECT BY level <=LENGTH('&Enter_text')
)a
WHERE a.col1 = 1;
That's fun, so I came out with something easily understandable using associative arrays as Hashmap; there's something subtle also with the non-case-sensitiveness:
CREATE OR REPLACE FUNCTION f(p_str in varchar2)
RETURN varchar2
AS
TYPE map_v IS TABLE OF integer INDEX BY varchar2(1);
l_dup map_v;
i PLS_INTEGER;
l_c varchar2(1);
l_tc varchar2(1);
l_nb_occurrences integer := NULL;
l_out_sngl varchar2(2000) := '';
l_out_dupl varchar2(2000) := '';
BEGIN
-- l_dup('a'):=0;
-- l_dup('b'):=0;
-- first loop to count occurrences
i:=1;
LOOP
l_c := lower(substr(p_str, i, 1));
begin
l_nb_occurrences := l_dup(l_c);
l_dup(l_c) := l_nb_occurrences + 1;
dbms_output.put_line(l_c||':incr:'||i);
exception
when no_data_found then
l_dup(l_c) := 1;
dbms_output.put_line(l_c||':pushed:'||i);
when others then
raise;
end;
i := i+1;
EXIT WHEN i > length(p_str);
END LOOP;
-- second loop for building output
i:=1;
LOOP
l_c := lower(substr(p_str, i, 1));
l_tc := substr(p_str, i, 1);
begin
l_nb_occurrences := l_dup(l_c);
dbms_output.put_line(l_c||':xx:'||i||'||'||l_nb_occurrences);
if l_nb_occurrences = 1 then
l_out_sngl := l_out_sngl || l_tc;
else
l_out_dupl := l_out_dupl || l_tc;
end if;
exception
when no_data_found then
dbms_output.put_line('why? there should be (see first loop).');
when others then
raise;
end;
i := i+1;
EXIT WHEN i > length(p_str);
END LOOP;
return l_out_sngl || l_out_dupl;
exception
when others then
dbms_output.put_line(sqlerrm);
END f;
/
Which gives results:
select f('Array') from dual;
-- yArra
select f('Bubbles') from dual;
-- ulesBbb
Try this function...
CREATE OR REPLACE FUNCTION non_repeating_char_first (v_input IN varchar2)
RETURN varchar2
IS
str1 varchar2 (100);
str2 varchar2 (100);
v_match number;
v_output varchar2 (100);
BEGIN
str1 := '';
str2 := '';
FOR i IN 1 .. LENGTH (v_input)
LOOP
SELECT REGEXP_COUNT (v_input,
SUBSTR (v_input, i, 1),
1,
'i')
INTO v_match
FROM DUAL;
IF v_match =1 THEN
str1 :=str1||SUBSTR (v_input, i, 1);
else
str2 :=str2||SUBSTR (v_input, i, 1);
END IF;
END LOOP;
v_output:=str1||str2;
RETURN v_output;
END;

custom split function not working in pl/sql

I have written a custom function in pl/sql which splits a clob variable into set of strings on the basis of the separator provided but it is not working as intended please help me out in figuring the problem with the code.
create or replace
FUNCTION SPLITCLOB (p_in_string clob, p_delim VARCHAR2) RETURN t_array
IS
i number :=0;
pos number :=0;
lv_str clob := p_in_string;
Strings t_array:=t_array ();
Begin
-- determine first chuck of string
pos := DBMS_LOB.INSTR(lv_str,p_delim,1,1);
If ( pos = 0) Then
i := i + 1;
strings.Extend();
strings(i) := DBMS_LOB.SUBSTR(lv_str,1);
RETURN strings;
End If;
-- while there are chunks left, loop
WHILE ( pos != 0) LOOP
-- increment counter
i := i + 1;
-- create array element for chuck of string
strings.EXTEND();
strings(i) := DBMS_LOB.SUBSTR(lv_str,1,pos-1);
-- remove chunk from string
lv_str := DBMS_LOB.SUBSTR(lv_str,pos+1,dbms_lob.getLength(lv_str));
-- determine next chunk
pos := DBMS_LOB.INSTR(lv_str,p_delim,1,1);
-- no last chunk, add to array
IF pos = 0 THEN
strings.extend();
strings(i+1) := lv_str;
END IF;
End Loop;
-- return array
RETURN strings;
End Splitclob;
here t_array() is custom type defiition of which is provided below
create or replace
TYPE "T_ARRAY"
AS TABLE OF VARCHAR2(500);
thanks in advance
I've changed a piece of code I wrote not a long time ago and it seems it works as you want. Note: it will also work if there is only a single item in the input parameter (I mean, the situation when there is no separator in the input is covered).
CREATE OR REPLACE
TYPE T_ARRAY
AS TABLE OF VARCHAR2(500);
/
CREATE OR REPLACE FUNCTION splitclob (p_clob_in CLOB, p_delim VARCHAR2) RETURN t_array
AS
v_buffer VARCHAR2(500);
v_len NUMBER;
v_offset NUMBER := 1;
v_delim_pos NUMBER;
v_amount NUMBER;
v_result_array t_array := t_array();
BEGIN
IF p_clob_in IS NOT NULL THEN
v_len := dbms_lob.getlength(p_clob_in);
WHILE v_offset < v_len
LOOP
v_delim_pos := instr(p_clob_in, p_delim, v_offset);
IF v_delim_pos = 0 THEN
v_amount := v_len - v_offset + 1;
ELSE
v_amount := v_delim_pos - v_offset;
END IF;
dbms_lob.read(p_clob_in, v_amount, v_offset, v_buffer);
v_offset := v_offset + v_amount + 1;
v_result_array.EXTEND;
v_result_array(v_result_array.LAST) := v_buffer;
END LOOP;
END IF;
RETURN v_result_array;
END;
/
DECLARE
v_array t_array;
BEGIN
v_array := splitclob('order_id=383325; order_line_item=59; order_class_id=5749; subscription_code=5U12PFNCAU; start_date=01/12/2012; end_date=30/11/2013; date_override=null; license_available=-1; license_consumed=1; license_override=-1; order_class_status=0', ';');
FOR v_i IN v_array.first..v_array.last
LOOP
dbms_output.put_line(v_i || ' ' || v_array(v_i));
END LOOP;
END;
/
Output:
1 order_id=383325
2 order_line_item=59
3 order_class_id=5749
4 subscription_code=5U12PFNCAU
5 start_date=01/12/2012
6 end_date=30/11/2013
7 date_override=null
8 license_available=-1
9 license_consumed=1
10 license_override=-1
11 order_class_status=0
You have to take into account a situation when one of the tokens will be longer than 500 characters. Remember that VARCHAR2 datatype in SQL context has a 4000 bytes limitation. If you know there won't be tokens longer than that, then you don't have to change anything.

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