How to write a PL/SQL function that:
Uses only numeric datatypes and functions-no VARCHAR2, CHAR, CLOB, XML, etc.
" In other words, character/string functions such as REVERSE, SUBSTR, etc are not to be used in your solution.
Accepts a PLS_INTEGER parameter
If the provided value is less than or equal to zero throws application error -20001 and provides a good error message
Returns a PLS_INTEGER value that has the digits from the input parameter in reverse order. If the input value ends in one or more zeroes those zeroes will not appear in the returned numeric value, since they would be leading zeroes.
Here is what I have written so far:
create or replace function test_reverse
(p_input in pls_integer)
return pls_integer
is
p_num pls_integer := 0;
p_in_num pls_integer := 0;
p_out_num pls_integer := 0;
begin
p_num := p_input;
loop
exit when p_num p_in_num := mod(p_num,10);
p_out_num := (p_out_num * 10) + p_in_num;
p_num := trunc(p_num / 10);
end loop;
return p_out_num;
end;
The problem is to turn 1234 into 4321. The string-y solution (admittedly using an undocumented built-in) is simplicity itself: to_number(reverse(to_char(1234))). Sticking to numeric datatypes is more cumbersome. My solution is very procedural: undoubtedly more elegant solutions exist.
Anyway, to turn 1234 into 4321 we need to generate 1 + 20 + 300 + 4000. My solution isolates each value in turn and multiplies by the appropriate power of ten. To isolate the values we use trunc() with a negative value. This rounds down the number to the left of the decimal point. Thus, trunc(1234,-3) produces 1000. To convert this into the required value we multiply by ten to the power of minus three. Thus 1000 * 10(-3) = 1.
The function iterates through the numbers. Having converted 1000 to 1 we calculate the remainder, 1234 - 1000 = 234. So now we need to isolate the 200 and convert it to 20; that is trunc(234, -2) and power(200, -1). So we can the offset fed into trunc() decrements by 1 and the exponent fed into power() increments by 2.
Here is a working function (very loosely based on the one you posted):
create or replace function test_reverse
(p_input in pls_integer)
return pls_integer
is
p_out_num pls_integer := 0;
offset pls_integer;
tgt pls_integer;
rmdr pls_integer;
exp pls_integer;
begin
rmdr := p_input;
offset := length(p_input)-1;
exp := -offset;
loop
tgt := trunc(rmdr, -offset);
p_out_num := p_out_num + (tgt * power(10, exp));
exp := exp + 2;
rmdr := rmdr - tgt;
exit when offset = 0;
offset := offset-1;
end loop;
return p_out_num;
end test_reverse;
/
Here is a LiveSQL demo (free Oracle Technet account required, alas). That doesn't feature the parameter validation but it is straightforward enough:
create or replace function test_reverse
(p_input in pls_integer)
return pls_integer
is
p_out_num pls_integer := 0;
offset pls_integer;
tgt pls_integer;
rmdr pls_integer;
exp pls_integer;
begin
if p_input <= 0 then
raise_application_error(-20001
'invalid input: ' || || ' must be greater than zero.'
);
end if;
rmdr := p_input;
....
I like the use of the modulo function as the op suggested:
CREATE OR REPLACE FUNCTION test_reverse (
p_input IN PLS_INTEGER
) RETURN PLS_INTEGER
IS
remain PLS_INTEGER := p_input;
retval PLS_INTEGER := 0;
BEGIN
IF p_input < 1 THEN
raise_application_error(-20001,'error: input must me greater than 0');
END IF;
LOOP
retval := retval * 10 + MOD(remain,10);
remain := trunc(remain / 10);
EXIT WHEN remain = 0;
END LOOP;
RETURN retval;
END;
/
This also avoids the use of the length() function which to my taste feels a bit "stringish", even if it operates on numbers.
Related
enter image description here
declare
str varchar2(2000) := :inputstr;
v_len number;
currChar CHAR(1);
begin
v_len := length(str);
for i in 1..v_len
Loop
currChar := substr(str,i,1);
if currChar = 1 then
dbms_output.put_line('curr index' || i);
end if;
End loop;
end;
When I pass '000111000' as input to IN_STRING variable , it trims the string and behaves very unusually.Please suggest some good approaches to iterate over binary strings like this.I am expecting output as 4,5,6 from above operation.
EDIT1:
Please don't directly input the string as str varchar2(2000) := '000111000';
Instead input it from bind variable as I mentioned above.
Your code works so long as you pass in a VARCHAR2 data type (and not a NUMBER).
You can also tidy up the code passing in the bind variable only once and using CONSTANTs to hold the values that are constant:
VARIABLE in_string VARCHAR2;
DECLARE
c_string CONSTANT VARCHAR2(200) := :in_string;
c_length CONSTANT PLS_INTEGER := LENGTH(c_string);
v_out CHAR(1);
BEGIN
FOR i IN 1..c_length
LOOP
v_out := SUBSTR(c_string,i,1) ;
DBMS_OUTPUT.PUT_LINE(v_out);
END LOOP;
END;
/
Which outputs:
0
0
1
1
1
0
0
0
db<>fiddle here
Shouldn't behave unusual, unless datatype of in_string variable is NUMBER (then leading zeros don't have any meaning) - switch to VARCHAR2.
Illustration:
NUMBER variable datatype
value you enter
result - really, missing leading zeros
Otherwise, it works OK (this is SQL*Plus so I used substitution variable):
SQL> DECLARE
2 v_length NUMBER (10);
3 v_out VARCHAR2 (20);
4 BEGIN
5 v_length := LENGTH ( '&&in_string');
6
7 FOR i IN 1 .. v_length
8 LOOP
9 v_out := SUBSTR ( '&&in_string', i, 1);
10 DBMS_OUTPUT.PUT_LINE (v_out);
11 END LOOP;
12 END;
13 /
Enter value for in_string: 00111000
0
0
1
1
1
0
0
0
PL/SQL procedure successfully completed.
Another option (if you're interested in it) doesn't require PL/SQL:
SQL> SELECT SUBSTR ( '&&in_string', LEVEL, 1) val
2 FROM DUAL
3 CONNECT BY LEVEL <= LENGTH ( '&&in_string');
V
-
0
0
1
1
1
0
0
0
8 rows selected.
SQL>
Can someone tell me what's wrong in my code. I need to create function that displays the number of digits given a number but I keep getting missing in and out parameter. Im am using Oracle SQL. Thank you
SET SERVEROUTPUT ON;
CREATE OR REPLACE FUNCTION Digit (n1 IN OUT INTEGER) RETURN INTEGER IS
Counter INTEGER := 0;
BEGIN
WHILE (n1 != 0 ) LOOP
n1 := n1 /10;
Counter := Counter + 1;
END LOOP;
RETURN Counter;
END;
Test block:
DECLARE
n1 INTEGER := 0;
BEGIN:
n1 := &n1;
DBMS_OUTPUT.PUT_LINE('The number of digit = ' ||Digit(Counter));
END;
The error is probably because of the stray : character after begin in your test block.
I would write it like this:
create or replace function digits
( p_num integer )
return integer
as
pragma udf;
i simple_integer := p_num;
l_digits simple_integer := 0;
begin
while i <> 0 loop
i := i / 10;
l_digits := l_digits + 1;
end loop;
return l_digits;
end digits;
I made the parameter in only, instead of in out. This means you can use it in SQL queries, and also in PL/SQL code without needing to pass in a variable whose value will get changed to 0 by the function.
pragma pdf tells the compiler to optimise the function for use in SQL.
I used simple_integer as in theory it's slightly more efficient for arithmetic operations, although I doubt any improvement is measurable in the real world (and I'm rather trusting the optimising compiler to cast my literal 10 as a simple_integer, as otherwise the overhead of type conversion will defeat any arithmetic efficiency).
I am tring to create update statement for a column which datatype is CLOB.
For this i am fetching XML from table and writing it in oracle console for later use.
For some of the data it is working fine.
For some i am getting error as
Exception1 : CREATE_UPDATE_XML_QUERY('600264','700009');
--ORA-06502: PL/SQL: numeric or value error
I have changed datatype of V_XML,V_BLOCK nothing has worked
PROCEDURE CREATE_UPDATE_XML_QUERY
(
MY_ID NUMBER,
MY_ID2 NUMBER
) AS
V_SCREEN_VERSION NUMBER;
V_XML_ID NUMBER;
V_CNT NUMBER;
V_XML CLOB);
V_BLOCK CLOB;
BEGIN
SELECT XML,XMLID INTO V_XML,V_XML_ID
FROM XML_TABLE WHERE ENC_ID = MY_ID AND SCREEN_ID = MY_ID2 ; ----getting excption
V_BLOCK :=
'
SET SERVEROUTPUT ON;
DECLARE
V_XML CLOB ;
BEGIN
';
V_BLOCK := V_BLOCK||'V_XML := '''||V_XML||''';';
V_BLOCK := V_BLOCK||'
UPDATE XML_TABLE SET XML = '||'V_XML'||'
WHERE ENC_ID = '||MY_ID||' AND ENC_TYPE = ''P'' AND SCREEN_ID = '||MY_ID2||' AND XMLID = '||V_XML_ID||';
--DBMS_OUTPUT.PUT_LINE(''V_XML =>''||V_XML);
DBMS_OUTPUT.PUT_LINE(''ROWCOUNT =>''||SQL%ROWCOUNT);
END;
/';
DBMS_OUTPUT.PUT_LINE('--Printing Annomous Block the XML :->>');
DBMS_OUTPUT.PUT_LINE(V_BLOCK);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Exception1 : UPDATE_SCREEN_MASTER_XML('''||MY_ID||''','''||MY_ID2||''','''||V_XML_ID||'''); --'||SQLERRM);--'||SQLERRM);
END CREATE_UPDATE_XML_QUERY;
How can i avoid error.
Is it because my XML is too big.
Well, I've come up with a test case to reproduce this (Oracle 12.2.0.1), and you're right, the problem isn't the DBMS_OUTPUT line.
declare
v_clob clob;
xmlid number;
begin
-- initialize clob and make clob a string of length 32768
dbms_lob.createtemporary(v_clob, true);
for i in 1..32768 loop
v_clob := v_clob || 'x';
end loop;
dbms_output.put_line(length(v_clob));
-- testing:
v_clob := v_clob || 'x'; -- appending a varchar2 works fine
v_clob := v_clob || xmlid; -- appending a number gives ORA-06502
v_clob := v_clob || 'x' || xmlid; -- appending a string+number still gives ORA-06502
v_clob := v_clob || to_clob(xmlid); -- works fine
dbms_lob.append(v_clob, 'x' || xmlid); -- also works fine
dbms_output.put_line(length(v_clob));
dbms_output.put_line(substr(v_clob,1,32767));
end;
/
The problem seems to be that when you concatenate strings with pipes, Oracle can append 2 clobs together if one of them is over 32k, and it can implicitly convert a varchar2 to a clob and append them. But if you try to append a number to a clob over 32k, it fails. It understands how to append varchar2 and number, and clob and clob, and clob and varchar2. But it can't seem to automatically figure out how to do number -> varchar2 -> clob. You can fix it by wrapping the string in to_clob(), avoiding the issue with Oracle's implicit conversion.
Where you get the error depends on the length of your table's XML CLOB value.
If the XML is more than 32k then you'll see the error at line 27 in your code, from trying to concatenate a varchar2 string a number (as #kfinity demonstrated) onto a CLOB; behaviour that isn't explained in the documentation, but is presumably something to do with implicit conversion since just explicitly converting the numbers with to_char(MY_ID) (or to_clob(MY_ID)).
If the XML is less than, but close to, 32k then you'll just scrape past that, but the V_BLOCK CLOB still ends up as greater than 32k, then will still error at line 39 as dbms_output can't handle that.
You can avoid the first problem by using to_char() around the numeric variables, or by using dbms_lob.append instead of concatenation:
...
V_BLOCK :=
'
SET SERVEROUTPUT ON;
DECLARE
V_XML CLOB ;
BEGIN
';
dbms_lob.append(V_BLOCK, 'V_XML := '''||V_XML||''';');
dbms_lob.append(V_BLOCK, '
UPDATE XML_TABLE SET XML = '||'V_XML'||'
WHERE ENC_ID = '||MY_ID||' AND ENC_TYPE = ''P'' AND SCREEN_ID = '||MY_ID2||' AND XMLID = '||V_XML_ID||';
--DBMS_OUTPUT.PUT_LINE(''V_XML =>''||V_XML);
DBMS_OUTPUT.PUT_LINE(''ROWCOUNT =>''||SQL%ROWCOUNT);
END;
/');
...
And you can avoid the second problem, as long as your XML value contains line breaks, by splitting the CLOB into lines, as shown here, but with a slight modification to handle empty lines; with additional variables declared as:
V_BUFFER VARCHAR2(32767);
V_AMOUNT PLS_INTEGER;
V_POS PLS_INTEGER := 1;
then instead of:
DBMS_OUTPUT.PUT_LINE(V_BLOCK);
you can do:
WHILE V_POS < length(V_BLOCK) LOOP
-- read to next newline if there is one, rest of CLOB if not
IF dbms_lob.instr(V_BLOCK, chr(10), V_POS) > 0 THEN
V_AMOUNT := dbms_lob.instr(V_BLOCK, chr(10), V_POS) - V_POS;
IF V_AMOUNT = 0 THEN
V_BUFFER := null; -- first character is a new line (i.e. a blank line)
ELSE
dbms_lob.read(V_BLOCK, V_AMOUNT, V_POS, V_BUFFER);
END IF;
V_POS := V_POS + V_AMOUNT + 1; -- skip newline character
ELSE
V_AMOUNT := 32767;
dbms_lob.read(V_BLOCK, V_AMOUNT, V_POS, V_BUFFER);
V_POS := V_POS + V_AMOUNT;
END IF;
DBMS_OUTPUT.PUT_LINE(V_BUFFER);
END LOOP;
db<>fiddle
#VinayakDwivedi edited to add a function to use instead:
PROCEDURE print_clob_to_output (p_clob IN CLOB)
IS
v_offset NUMBER := 1;
v_chunk_size NUMBER := 10000;
BEGIN
LOOP
EXIT WHEN v_offset > DBMS_LOB.getlength (p_clob);
DBMS_OUTPUT.put_line (
DBMS_LOB.SUBSTR (p_clob, v_chunk_size, v_offset));
v_offset := v_offset + v_chunk_size;
END LOOP;
END print_clob_to_output;
... but this will introduce extra line breaks every 10000 characters.
However, it's worth noting that the PL/SQL block you are generating within that ends up with a line like:
V_XML := '<original xml from table>';
and if that generated code is run it will also error if the original XML is more that 32k. Really that generated code also needs to be broken up to reconstruct your CLOB in chunks - i.e. a loop that takes 32k at a time and concatenates/appends those chunks to reconstitute the full value. It also has whitespace at the start of each line so the DECLARE etc. and more importantly the final / are not at the start of their respective lines, which will also cause problems when trying to run it as-is.
Check out:
https://www.techonthenet.com/oracle/errors/ora06502.php
This suggests that there may be 3 possible causes to this error
Number to big - I doubt if this is your issue, as the max numbers are pretty huge
Conversion error - you are trying to convert a non number to a number
Assigning NULL to a NOT NULL constrained variable - self explantory
without knowing more of the context it is not really possible to determine which of these is your issue.
Hope this helps!
How can i achieve below functionality using Oracle SQL or PL/SQL?
This stored procedure gives the same result as NORMDIST function in Calc.
The parameters that need to be passed are x, mean, standard deviation and cumulative.
Cumulative parameter gives a choice to get normal distribution value at x (0) or cumulative probability of value<=x (1).
create or replace FUNCTION NORMDIST(x_value number,mean_value number,stddev_value number, cumulative NUMBER DEFAULT 0)
RETURN NUMBER IS
x number;
t number;
z number;
ans number;
BEGIN
IF (stddev_value = 0) THEN
RETURN 1;
END IF;
x := (x_value-mean_value)/stddev_value;
IF cumulative = 1 THEN
z := abs(x)/SQRT(2);
t := 1/(1+0.5*z);
ans := t*exp(-z*z-1.26551223+t*(1.00002368+t*(0.37409196+t*(0.09678418+t*(-0.18628806+t*(0.27886807+t*(-1.13520398+t*(1.48851587+t*(-0.82215223+t*0.17087277)))))))))/2;
If (x <= 0)
Then RETURN ans;
Else return 1-ans;
End if;
ELSE
RETURN 1/(sqrt(2*3.14159265358979)*stddev_value)*Exp(-(Power(x_value-mean_value,2)/(2*Power(stddev_value,2)) ));
END IF;
END;
/
This is a quick solution, I have not tried to gain maximum precision or performance. Depending on your req, you might need to tweak number format, precision, calculation logic, etc.
create or replace function calc_sn_pdf(x in number) return number
is
pi CONSTANT NUMBER := 3.14159265358979;
begin
return 1/sqrt(2*pi) * exp(-x*x/2);
end;
/
The cdf must be approximated (as it is az integral function which has no simple mathematical formula), one possible approximation is implemented as follows. Many other approximations can be found on Wikipedia.
create or replace function calc_sn_cdf(x in number) return number
is
b0 CONSTANT NUMBER := 0.2316419;
b1 CONSTANT NUMBER := 0.319381530;
b2 CONSTANT NUMBER := -0.356563782;
b3 CONSTANT NUMBER := 1.781477937;
b4 CONSTANT NUMBER := -1.821255978;
b5 CONSTANT number := 1.330274429;
v_t number;
begin
--see 26.2.17 at http://people.math.sfu.ca/~cbm/aands/page_932.htm
--see https://en.wikipedia.org/wiki/Normal_distribution#Numerical_approximations_for_the_normal_CDF
--Zelen & Severo (1964) approximation
if x < 0 then
--this approximation works for x>0, but cdf is symmetric for x=0:
return 1 - calc_sn_cdf(-x);
else
v_t := 1 / (1 + b0*x);
return 1 - calc_sn_pdf(x)*(b1*v_t + b2*v_t*v_t + b3*v_t*v_t*v_t + b4*v_t*v_t*v_t*v_t + b5*v_t*v_t*v_t*v_t*v_t);
end if;
end;
/
Btw, if you need to run these functions a lot of time, it would be useful to turn on native pl/sql compilation.
--I wrote this function in PL/SQL and it works. I compared results with the NORMDIST
--Function in excel and the results match very closely. You will need to pass the
--following --parameters to the function.
-- 1. Value of X
-- 2. Value of Mean
-- 3. Value of Standard Deviation
--This function returns the same result when you pass cumulative=TRUE in excel.
create or replace FUNCTION NORMSDIST(x_value number,mean_value number,stddev_value number)
RETURN NUMBER IS
x number;
t number;
z number;
ans number;
BEGIN
IF (stddev_value = 0) THEN
RETURN 1;
END IF;
x := (x_value-mean_value)/stddev_value;
z := abs(x)/SQRT(2);
t := 1.0/(1.0+0.5*z);
ans := t*exp(-z*z-1.26551223+t*(1.00002368+t*(0.37409196+t*(0.09678418+t*(-0.18628806+t*(0.27886807+t*(-1.13520398+t*(1.48851587+t*(-0.82215223+t*0.17087277)))))))))/2.0;
If (x <= 0)
Then RETURN ans;
Else return 1-ans;
End if;
END NORMSDIST;
I got the below sql base64 decoder from stackoverflow (forgot the link). I have slightly modified to my requirement. When I try to execute, the decoding is only partially working. I have max of 10MB worth of base64 encoded clob data per row in my DB.
Not sure what is that I am missing.
create or replace FUNCTION Decode64_CLOB3(IN_CLOB CLOB) RETURN CLOB IS
clobOriginal clob;
clobInBase64 clob;
substring varchar2(2000);
n pls_integer := 0;
substring_length pls_integer := 2000;
function from_base64(t in varchar2) return varchar2 is
begin
return
utl_raw.cast_to_varchar2(utl_encode.base64_decode(utl_raw.cast_to_raw(t)));
end from_base64;
begin
select myCLobData into clobInBase64 from myClobTable where ID = 3;
clobInBase64 :=IN_CLOB;
n := 0;
clobOriginal := null;
while true loop
substring := dbms_lob.substr(clobInBase64,
least(substring_length, length(clobInBase64) - (substring_length * n + 1)));
if substring is null then
DBMS_OUTPUT.put_line('this is me 2');
exit;
end if;
clobOriginal := clobOriginal || from_base64(substring);
n := n + 1;
end loop;
return clobOriginal;
end;
update2
I did some debugging and found out that the issue is with the substring.
The substring works fine for the firstloop of (2000) char. but its not able to move the the next 2000 char. Not sure what is the problem. Can some one advise.
You've changed the call to dbms_lob.substr() and in doing so you've removed the third argument, which is the offset. Since you aren't providing that it defaults to 1, so every time around the loop you're getting the same value - the first 2000 characters from your encoded string.
Well, you'll get 2000 for lots of calls, then once n gets big enough you'll get null, with the code you've posted. Since you said it goes into a 'hung' state that suggests what you're running is slightly different.
With some slight modifications this seemed to work:
create or replace function decode64_clob3(p_clob_encoded clob)
return clob
is
l_clob_decoded clob;
substring varchar2(2048);
substring_length pls_integer := 2048;
n pls_integer := 0;
function from_base64(t in varchar2) return varchar2
is
begin
return utl_raw.cast_to_varchar2(utl_encode.base64_decode(utl_raw.cast_to_raw(t)));
end from_base64;
begin
dbms_lob.createtemporary(l_clob_decoded, false);
while true
loop
substring := dbms_lob.substr(p_clob_encoded, substring_length,
(substring_length * n) + 1);
if substring is null then
exit;
end if;
dbms_lob.append(l_clob_decoded, from_base64(substring));
n := n + 1;
/* protective check for infinite loop while debugging
if n > 10000 then
dbms_output.put_line('n is ' || n);
exit;
end if;
*/
end loop;
return l_clob_decoded;
end;
/
I changed the CLOB handling slightly; concatenation is fine but this is a little more explicit. I'm not sure I'd bother with the nested function though, personally. And I'm not sure the least() calculation is really adding anything.
But this can error with either ORA-06502: PL/SQL: numeric or value error: invalid LOB locator specified: ORA-22275 or ORA-14553: cannot perform a lob write operation inside a query if the base64-encoded value is line-wrapped, which is usually the case. The first chunk is decoded OK, but the next chunk starts with an unconsumed newline which throws everything off and produces garbage; eventually that garbage is null or some other value that causes one of those exceptions.
So you need to track the position as you move through the CLOB, in multiples of the line-wrap length; and adjust the tracked position to take extra newlines into account:
create or replace function decode64_clob3(p_clob_encoded clob)
return clob
is
l_clob_decoded clob;
l_substring varchar2(2048);
l_substring_length pls_integer := 2048;
l_pos pls_integer := 1;
begin
dbms_lob.createtemporary(l_clob_decoded, false);
while l_pos <= dbms_lob.getlength(p_clob_encoded)
loop
l_substring := dbms_lob.substr(p_clob_encoded, l_substring_length, l_pos);
if l_substring is null then
exit;
end if;
dbms_lob.append(l_clob_decoded,
utl_raw.cast_to_varchar2(utl_encode.base64_decode(utl_raw.cast_to_raw(l_substring))));
/* Adjust l_pos to skip over CR/LF chars if base64 is line-wrapped */
l_pos := l_pos + length(l_substring);
while dbms_lob.substr(p_clob_encoded, 1, l_pos) in (chr(10), chr(13)) loop
l_pos := l_pos + 1;
end loop;
end loop;
return l_clob_decoded;
end;
/
Or strip out any potential newline/carriage return characters first:
create or replace function decode64_clob3(p_clob_encoded clob)
return clob
is
l_clob_encoded clob;
l_clob_decoded clob;
l_substring varchar2(2048);
l_substring_length pls_integer := 2048;
l_pos pls_integer := 1;
begin
l_clob_encoded := replace(replace(p_clob_encoded, chr(10), null), chr(13), null);
dbms_lob.createtemporary(l_clob_decoded, false);
while l_pos <= dbms_lob.getlength(l_clob_encoded)
loop
l_substring := dbms_lob.substr(l_clob_encoded, l_substring_length, l_pos);
if l_substring is null then
exit;
end if;
dbms_lob.append(l_clob_decoded,
utl_raw.cast_to_varchar2(utl_encode.base64_decode(utl_raw.cast_to_raw(l_substring))));
l_pos := l_pos + length(l_substring);
end loop;
return l_clob_decoded;
end;
/
This works with wrapped and unwrapped base64 values.
set serveroutput on
with t (c) as (
select utl_raw.cast_to_varchar2(utl_encode.base64_encode(utl_raw.cast_to_raw('Hello world'))) from dual
)
select c, decode64_clob3(c)
from t;
C
--------------------------------------------------------------------------------
DECODE64_CLOB3(C)
--------------------------------------------------------------------------------
SGVsbG8gd29ybGQ=
Hello world
Also tested with encoded values that are larger than the substring_length value.