Multiple REPLACE function in Oracle - oracle

I am using the REPLACE function in oracle to replace values in my string like;
SELECT REPLACE('THE NEW VALUE IS #VAL1#','#VAL1#','55') from dual
So this is OK to replace one value, but what about 20+, should I use 20+ REPLACE function or is there a more practical solution.
All ideas are welcome.

Even if this thread is old is the first on Google, so I'll post an Oracle equivalent to the function implemented here, using regular expressions.
Is fairly faster than nested replace(), and much cleaner.
To replace strings 'a','b','c' with 'd' in a string column from a given table
select regexp_replace(string_col,'a|b|c','d') from given_table
It is nothing else than a regular expression for several static patterns with 'or' operator.
Beware of regexp special characters!

The accepted answer to how to replace multiple strings together in Oracle suggests using nested REPLACE statements, and I don't think there is a better way.
If you are going to make heavy use of this, you could consider writing your own function:
CREATE TYPE t_text IS TABLE OF VARCHAR2(256);
CREATE FUNCTION multiple_replace(
in_text IN VARCHAR2, in_old IN t_text, in_new IN t_text
)
RETURN VARCHAR2
AS
v_result VARCHAR2(32767);
BEGIN
IF( in_old.COUNT <> in_new.COUNT ) THEN
RETURN in_text;
END IF;
v_result := in_text;
FOR i IN 1 .. in_old.COUNT LOOP
v_result := REPLACE( v_result, in_old(i), in_new(i) );
END LOOP;
RETURN v_result;
END;
and then use it like this:
SELECT multiple_replace( 'This is #VAL1# with some #VAL2# to #VAL3#',
NEW t_text( '#VAL1#', '#VAL2#', '#VAL3#' ),
NEW t_text( 'text', 'tokens', 'replace' )
)
FROM dual
This is text with some tokens to replace
If all of your tokens have the same format ('#VAL' || i || '#'), you could omit parameter in_old and use your loop-counter instead.

Bear in mind the consequences
SELECT REPLACE(REPLACE('TEST123','123','456'),'45','89') FROM DUAL;
will replace the 123 with 456, then find that it can replace the 45 with 89.
For a function that had an equivalent result, it would have to duplicate the precedence (ie replacing the strings in the same order).
Similarly, taking a string 'ABCDEF', and instructing it to replace 'ABC' with '123' and 'CDE' with 'xyz' would still have to account for a precedence to determine whether it went to '123EF' or ABxyzF'.
In short, it would be difficult to come up with anything generic that would be simpler than a nested REPLACE (though something that was more of a sprintf style function would be a useful addition).

In case all your source and replacement strings are just one character long, you can simply use the TRANSLATE function:
SELECT translate('THIS IS UPPERCASE', 'THISUP', 'thisup')
FROM DUAL
See the Oracle documentation for details.

This is an old post, but I ended up using Peter Lang's thoughts, and did a similar, but yet different approach. Here is what I did:
CREATE OR REPLACE FUNCTION multi_replace(
pString IN VARCHAR2
,pReplacePattern IN VARCHAR2
) RETURN VARCHAR2 IS
iCount INTEGER;
vResult VARCHAR2(1000);
vRule VARCHAR2(100);
vOldStr VARCHAR2(50);
vNewStr VARCHAR2(50);
BEGIN
iCount := 0;
vResult := pString;
LOOP
iCount := iCount + 1;
-- Step # 1: Pick out the replacement rules
vRule := REGEXP_SUBSTR(pReplacePattern, '[^/]+', 1, iCount);
-- Step # 2: Pick out the old and new string from the rule
vOldStr := REGEXP_SUBSTR(vRule, '[^=]+', 1, 1);
vNewStr := REGEXP_SUBSTR(vRule, '[^=]+', 1, 2);
-- Step # 3: Do the replacement
vResult := REPLACE(vResult, vOldStr, vNewStr);
EXIT WHEN vRule IS NULL;
END LOOP;
RETURN vResult;
END multi_replace;
Then I can use it like this:
SELECT multi_replace(
'This is a test string with a #, a $ character, and finally a & character'
,'#=%23/$=%24/&=%25'
)
FROM dual
This makes it so that I can can any character/string with any character/string.
I wrote a post about this on my blog.

I have created a general multi replace string Oracle function by a table of varchar2 as parameter.
The varchar will be replaced for the position rownum value of table.
For example:
Text: Hello {0}, this is a {2} for {1}
Parameters: TABLE('world','all','message')
Returns:
Hello world, this is a message for all.
You must create a type:
CREATE OR REPLACE TYPE "TBL_VARCHAR2" IS TABLE OF VARCHAR2(250);
The funcion is:
CREATE OR REPLACE FUNCTION FN_REPLACETEXT(
pText IN VARCHAR2,
pPar IN TBL_VARCHAR2
) RETURN VARCHAR2
IS
vText VARCHAR2(32767);
vPos INT;
vValue VARCHAR2(250);
CURSOR cuParameter(POS INT) IS
SELECT VAL
FROM
(
SELECT VAL, ROWNUM AS RN
FROM (
SELECT COLUMN_VALUE VAL
FROM TABLE(pPar)
)
)
WHERE RN=POS+1;
BEGIN
vText := pText;
FOR i IN 1..REGEXP_COUNT(pText, '[{][0-9]+[}]') LOOP
vPos := TO_NUMBER(SUBSTR(REGEXP_SUBSTR(pText, '[{][0-9]+[}]',1,i),2, LENGTH(REGEXP_SUBSTR(pText, '[{][0-9]+[}]',1,i)) - 2));
OPEN cuParameter(vPos);
FETCH cuParameter INTO vValue;
IF cuParameter%FOUND THEN
vText := REPLACE(vText, REGEXP_SUBSTR(pText, '[{][0-9]+[}]',1,i), vValue);
END IF;
CLOSE cuParameter;
END LOOP;
RETURN vText;
EXCEPTION
WHEN OTHERS
THEN
RETURN pText;
END FN_REPLACETEXT;
/
Usage:
TEXT_RETURNED := FN_REPLACETEXT('Hello {0}, this is a {2} for {1}', TBL_VARCHAR2('world','all','message'));

Thanks for the answer. Rather than specifying the translation in the call, you can also do this using a cursor as shown below.
create or replace function character_substitutions (input_str varchar2)
return varchar2
as
v_result VARCHAR2(4000);
cursor c_translate_table is
select '&' as symbol_to_replace, 'amp' as symbol_in_return_string from dual
union all
select '/' as symbol_to_replace, '_' as symbol_in_return_string from dual
union all
select '"' as symbol_to_replace, 'in' as symbol_in_return_string from dual
union all
select '%' as symbol_to_replace, 'per' as symbol_in_return_string from dual
union all
select '.' as symbol_to_replace, '_' as symbol_in_return_string from dual;
begin
v_result := input_str;
for r_translate in c_translate_table loop
v_result := REPLACE( v_result, r_translate.symbol_to_replace, r_translate.symbol_in_return_string);
end loop;
return v_result;
end;
/

Another example of how to apply an ordered list of character replacements. In this case:
convert a backslash to a slash
convert a double slash occurrence to a single slash
convert a single quote to an escaped single quote
For example, convert from:
\\server\Folder\611891\Joe's Doc.pdf
to
/server/Folder/611891/Joe\'s Doc.pdf
Can use a recursive CTE to iterate through the ordered list in sequence. First part demonstrates how the string evolves:
WITH subs AS (
SELECT '\' convert_from, '/' convert_to, 1 seq FROM DUAL
UNION SELECT '//', '/', 2 FROM DUAL
UNION SELECT '''', '\''', 3 FROM DUAL
),
rcte(convert_from, convert_to, seq, src) AS (
SELECT '', '', 0, '\\server\Folder\611891\Joe''s Doc.pdf' src FROM DUAL
UNION ALL
SELECT s.convert_from, s.convert_to, s.seq, REPLACE(r.src, s.convert_from, s.convert_to)
FROM rcte r
JOIN subs s ON (s.seq = r.seq + 1)
)
SELECT *
FROM rcte
ORDER BY seq
;
| CONVERT_FROM | CONVERT_TO | SEQ | SRC |
|--------------|------------|-----|--------------------------------------|
| (null) | (null) | 0 | \\server\Folder\611891\Joe's Doc.pdf |
| \ | / | 1 | //server/Folder/611891/Joe's Doc.pdf |
| // | / | 2 | /server/Folder/611891/Joe's Doc.pdf |
| ' | \' | 3 | /server/Folder/611891/Joe\'s Doc.pdf |
But actually only interested in the final result:
SELECT src
FROM rcte
WHERE seq = (SELECT MAX(seq) FROM subs)
;
| SRC |
|--------------------------------------|
| /server/Folder/611891/Joe\'s Doc.pdf |

Related

Getting PLS-00103 Error. PLS-00103: Encountered the symbol "RETURN"

I am new to PL/sql and I am currently writing some PL/SQL code that has to extract data from two tables. The code that I have so far keeps getting error PLS-00103: Encountered the symbol "RETURN" when expecting one of
the following:
* & = - + ; < / > at in is mod remainder not rem
This is my code at the moment
CREATE OR REPLACE FUNCTION LISTNATION(N_NAME in VARCHAR2, R_NAME IN VARCHAR2, R_REGIONKEY IN NUMBER)
2 RETURN VARCHAR2 IS
3 R_REGIONKEY NUMBER(3);
4 R_NAME VARCHAR2(50);
5 N_NAME VARCHAR2(50);
6
7 BEGIN
8 select r_regionkey, r_name, n_name
9 from region
10 inner join nation
11 on r_regionkey = n_regionkey;
12
13 dbms_output.put_line = (R_REGIONKEY || ' ' || R_NAME || ':' || N_NAME || ',')
14
15 END;
16 /
The immediate cause of the error you're getting(in the original version of the question) is that you're missing missing a semicolon at the end of line 4.
But there are quite a few other issues:
you are using double-quotes " around string literals instead of single quotes '. Those are for identifiers, not strings.
your local variables are the same as the arguments, but you don't need all those arguments anyway.
you aren't selecting into anything; and your query will return multiple rows.
your query doesn't have a where clause so it will look for data in all regions.
you've mangled the dbms_output call; it's missing a closing parenthesis (again, in the original question) and should not have the =.
you're returning test but haven't declared that.
So, to have the function use dbms_output to display the results - which relies on the caller handling that output, which you shouldn't assume - you could maybe do:
CREATE OR REPLACE FUNCTION LISTNATION(P_R_REGIONKEY IN NUMBER)
RETURN VARCHAR2 IS
L_R_NAME REGION.R_NAME%TYPE;
L_N_NAME NATION.N_NAME%TYPE;
BEGIN
FOR l_row IN (
select r.r_name, n.n_name
from region r
inner join nation n
on r.r_regionkey = n.n_regionkey
where r.r_regionkey = p_r_regionkey
) LOOP
dbms_output.put_line (P_R_REGIONKEY || ' ' || l_row.R_NAME || ':' || l_row.N_NAME);
END LOOP;
RETURN 'test';
END;
/
That adds a filter to your query, turns it into an implicit cursor, and loops over the results.
db<>fiddle with some made-up data, with table and column names taken from your attempt.
It isn't clear what you actually want to return; you might want a comma-separated list of nation names, in which case look at the listagg() function. For example, you could do something like:
CREATE OR REPLACE FUNCTION LISTNATION(P_R_REGIONKEY IN NUMBER)
RETURN VARCHAR2 IS
L_RESULT VARCHAR2(4000);
BEGIN
select listagg(n.n_name, ',') within group (order by n.n_name)
into l_result
from region r
inner join nation n
on r.r_regionkey = n.n_regionkey
where r.r_regionkey = p_r_regionkey;
RETURN l_result;
END;
/
and you could then call that to get back a single list of values.
db<>fiddle
Although using a PL/SQL function wrapper around that query doesn't seem very useful. Presumably this is an exercise though...
is there anyway for this to display it such that it only shows the region key and region once while listing all nations?
You can change the second function to include the region info, concatenating that with the listagg result:
CREATE OR REPLACE FUNCTION LISTNATION(P_R_REGIONKEY IN NUMBER)
RETURN VARCHAR2 IS
L_RESULT VARCHAR2(4000);
BEGIN
select r.r_regionkey || ' ' || r.r_name || ': '
|| listagg(n.n_name, ',') within group (order by n.n_name)
into l_result
from region r
inner join nation n
on r.r_regionkey = n.n_regionkey
where r.r_regionkey = p_r_regionkey
group by r.r_regionkey, r.r_name;
RETURN l_result;
END;
/
then call that for each region key you're interested in.
db<>fiddle
The way you've phrased it makes it sound like you don't want to pass in a region key and instead want to see all regions at once; and want to use dbms_output, which isn't ideal; and don't really want to return anything. So you could use a procedure instead, change the cursor query to bring back the region name, and then concatenate in the put_line call inside the loop:
CREATE OR REPLACE PROCEDURE LISTNATION IS
BEGIN
FOR l_row IN (
select r.r_regionkey, r.r_name,
listagg(n.n_name, ',') within group (order by n.n_name) as names
from region r
inner join nation n
on r.r_regionkey = n.n_regionkey
group by r.r_regionkey, r.r_name
order by r.r_regionkey
) LOOP
dbms_output.put_line (l_row.R_REGIONKEY || ' ' || l_row.R_NAME || ': ' || l_row.names);
END LOOP;
END;
/
dbms_output:
1 EMEA: FRANCE,UNITED KINGDOM
2 APAC: CHINA
db<>fiddle
There are lots of variations of course, it depends exactly what you want to have happen, but you should be able to adapt one of these approaches. Another is to have a function or procedure generate a ref cursor, but again it's not clear what you want. But using dbms_output isn't a great idea, as the caller may not be using a client that looks for and displays that.

How we can get the value from string: 1;BWQTY;4|2;NGRID;34664 and store into a variable if any specific SUBSTRING found?

I have a table name as event_creation_template which having column as TEXT ,which contains value like below :
1;BWQTY;4|2;NGRID;34664
1;DTYPE;Amount|2;LOYAL;R
How we can get the value from string:
1;BWQTY;4|2;NGRID;34664
If string is having BWQTY then store 4 into variable again search for the NGRID ,if string contains NGRID , we need in 34664 into different variable.
using Oracle PL/SQL.
considering the string like 'BWQTY' will always lie between two non-aphanumeric characters and the corresponding values like 4 lies between either non-alphanumeric character or appears at the end like in the two examples above you can use below code
CREATE OR REPLACE PR_PREP
IS
V_VAR1 VARCHAR2(100);
V_-VAR2 VARCHAR2(100);
V_SEARCH_VAL1 VARCHAR2(100) := 'BWQTY';
V_SEARCH_VAL2 VARCHAR2(100) := 'NGRID';
CURSOR C1
IS
SELECT TEXT FROM EVENT_CREATION_TEMPLATE;
BEGIN
FOR REC IN C1
LOOP
IF INSTR(REC.TEXT,V_SEARCH_VAL1) > 0 THEN
SELECT NVL(SUBSTR(SUBSTR(REC.TEXT,INSTR(REC.TEXT,V_SEARCH_VAL1)+LENGTH(V_SEARCH_VAL1)+1), 1, REGEXP_INSTR( SUBSTR(REC.TEXT,INSTR(REC.TEXT,V_SEARCH_VAL1)+LENGTH(V_SEARCH_VAL1)+1), '[^0-9a-zA-Z]',1)-1), SUBSTR(REC.TEXT,INSTR(REC.TEXT,V_SEARCH_VAL1)+LENGTH(V_SEARCH_VAL1)+1))
INTO V_VAR1
FROM DUAL ;
END IF;
IF INSTR(REC.TEXT,V_SEARCH_VAL2) > 0 THEN
SELECT NVL(SUBSTR(SUBSTR(REC.TEXT,INSTR(REC.TEXT,V_SEARCH_VAL2)+LENGTH(V_SEARCH_VAL2)+1), 1, REGEXP_INSTR( SUBSTR(REC.TEXT,INSTR(REC.TEXT,V_SEARCH_VAL2)+LENGTH(V_SEARCH_VAL2)+1), '[^0-9a-zA-Z]',1)-1), SUBSTR(REC.TEXT,INSTR(REC.TEXT,V_SEARCH_VAL2)+LENGTH(V_SEARCH_VAL2)+1))
INTO V_VAR2
FROM DUAL ;
END IF;
-- DO FURTHER WORK
END LOOP;
END;
You can use regular expressions
for BWQTY:
select str,
SUBSTR(REGEXP_SUBSTR(str, '([BWQTY]{5})([;])([0-9]+)'),
INSTR(REGEXP_SUBSTR(str, '([BWQTY]{5})([;])([0-9]+)'), ';') + 1 ,
LENGTH(REGEXP_SUBSTR(str, '([BWQTY]{5})([;])([0-9]+)')) - INSTR(REGEXP_SUBSTR(str, '([BWQTY]{5})([;])([0-9]+)'), ';')) AS VALUE
from test
for NGRID:
select str,
SUBSTR(REGEXP_SUBSTR(str, '([NGRID]{5})([;])([0-9]+)'),
INSTR(REGEXP_SUBSTR(str, '([NGRID]{5})([;])([0-9]+)'), ';') + 1 ,
LENGTH(REGEXP_SUBSTR(str, '([NGRID]{5})([;])([0-9]+)')) - INSTR(REGEXP_SUBSTR(str, '([NGRID]{5})([;])([0-9]+)'), ';')) AS VALUE
from test
Where str is your input string...

Oracle regexp_like word boundaries multiple words workaround

As you know Oracle POSIX implementation of regexes does not support word boundaries. One workaround is suggested here:
Oracle REGEXP_LIKE and word boundaries
However it does not work if I want to, for instance select all 4 character strings. Consider this, for example:
myvar:=regexp_substr('test test','(^|\s|\W)[\S]{4}($|\s|\W)')
This obviously selects only the first occurrence. I do not know how to do this in the Oracle world, although normally it is simply (\b)[\S]{4}(\b). The problem is that most woraround rely on some nonexistent feature, like lookaround etc.
select xmlcast(xmlquery('for $token in ora:tokenize(concat(" ",$in)," ")
where string-length($token) = $size
return $token' passing 'test test' as "in", 4 as "size" returning content) as varchar2(2000)) word from dual;
Xquery and FLWOR expresion.
concat(" ",$in) - workaround if input string is null or it has only 1 matchting word.
ora:tokenize - tokenize string by "space"
string-length($token) = $size check if token has appropriate length.
xmlcast - convert xmltype to varchar2
Easy ? Any question:)
DECLARE
str VARCHAR2(200) := 'test test';
pattern VARCHAR2(200) := '(\w+)($|\s+|\W+)';
match VARCHAR2(200);
BEGIN
FOR i IN 1 .. REGEXP_COUNT( str, pattern ) LOOP
match := REGEXP_SUBSTR( str, pattern, 1, i, NULL, 1 );
IF LENGTH( match ) = 4 THEN
DBMS_OUTPUT.PUT_LINE( match );
END IF;
END LOOP;
END;
/
or (without using REGEXP_COUNT or the 6th parameter of REGEXP_SUBSTR that was introduced in 11G):
DECLARE
str VARCHAR2(200) := 'test test';
pattern CONSTANT VARCHAR2(3) := '\w+';
match VARCHAR2(200);
i NUMBER(4,0) := 1;
BEGIN
match := REGEXP_SUBSTR( str, pattern, 1, i );
WHILE match IS NOT NULL LOOP
IF LENGTH( match ) = 4 THEN
DBMS_OUTPUT.PUT_LINE( match );
END IF;
i := i + 1;
match := REGEXP_SUBSTR( str, pattern, 1, i );
END LOOP;
END;
/
Output:
test
test
If you want to use this in SQL then you can easily translate it into a pipelined function or a function that returns a collection.

Oracle PLSQL: Help Required: Parsing String Collection or Concatenated String

Whenever the length of string l_long_string is above 4000 characters, the following code is throwing an error:
ORA-01460: unimplemented or unreasonable conversion requested
Instead of the nested regexp_substr query, when I try to use
SELECT column_value
FROM TABLE(l_string_coll)
it throws:
ORA-22905: cannot access rows from a non-nested table item
How can I modify the dynamic query?
Notes:
- l_string_coll is of type DBMS_SQL.VARCHAR2S, and comes as input to my procedure (here, i have just shown as an anonymous block)
- I'll have to manage without creating a User-defined Type in DB schema, so I am using the in-built DBMS_SQL.VARCHAR2S.
- This is not the actual business procedure, but is close to this. (Can't post the original)
- Dynamic query has to be there since I am using it for building the actual query with session, current application schema name etc.
/*
CREATE TABLE some_other_table
(word_id NUMBER(10), word_code VARCHAR2(30), word VARCHAR2(255));
INSERT INTO some_other_table VALUES (1, 'A', 'AB');
INSERT INTO some_other_table VALUES (2, 'B', 'BC');
INSERT INTO some_other_table VALUES (3, 'C', 'CD');
INSERT INTO some_other_table VALUES (4, 'D', 'DE');
COMMIT;
*/
DECLARE
l_word_count NUMBER(10) := 0;
l_counter NUMBER(10) := 0;
l_long_string VARCHAR2(30000) := NULL;
l_dyn_query VARCHAR2(30000) := NULL;
l_string_coll DBMS_SQL.VARCHAR2S;
BEGIN
-- l_string_coll of type DBMS_SQL.VARCHAR2S comes as Input to the procedure
FOR i IN 1 .. 4100
LOOP
l_counter := l_counter + 1;
l_string_coll(l_counter) := 'AB';
END LOOP;
-- Above input collection is concatenated into CSV string
FOR i IN l_string_coll.FIRST .. l_string_coll.LAST
LOOP
l_long_string := l_long_string || l_string_coll(i) || ', ';
END LOOP;
l_long_string := TRIM(',' FROM TRIM(l_long_string));
dbms_output.put_line('Length of l_long_string = ' || LENGTH(l_long_string));
/*
Some other tasks in PLSQL done successfully using the concatenated string l_long_string
*/
l_dyn_query := ' SELECT COUNT(*)
FROM some_other_table
WHERE word IN ( SELECT TRIM(REGEXP_SUBSTR(str, ''[^,]+'', 1, LEVEL)) word
FROM ( SELECT :string str FROM SYS.DUAL )
CONNECT BY TRIM(REGEXP_SUBSTR(str, ''[^,]+'', 1, LEVEL)) IS NOT NULL )';
--WHERE word IN ( SELECT column_value FROM TABLE(l_string_coll) )';
EXECUTE IMMEDIATE l_dyn_query INTO l_word_count USING l_long_string;
dbms_output.put_line('Word Count = ' || l_word_count);
EXCEPTION
WHEN OTHERS
THEN
dbms_output.put_line('SQLERRM = ' || SQLERRM);
dbms_output.put_line('FORMAT_ERROR_BAKCTRACE = ' || dbms_utility.format_error_backtrace);
END;
/
How can I modify the dynamic query?
First of all. Based on the code you've provided, there is absolutely no need to use dynamic, native or DBMS_SQL dynamic SQL at all.
Secondly, SQL cannot operate on "strings" that are greater than 4K bytes in length(Oracle versions prior to 12c), or 32K bytes(Oracle version 12cR1 and up, if MAX_STRING_SIZE initialization parameter is set to EXTENDED).
PL/SQL, on the other hand, allows you to work with varchar2() character strings that are greater than 4K bytes (up to 32Kb) in length. If you just need to count words in a comma separated sting, you can simply use regexp_count() regular expression function(Oracle 11gr1 and up) as follows:
set serveroutput on;
set feedback off;
clear screen;
declare
l_str varchar2(100) := 'aaa,bb,ccc,yyy';
l_numOfWords number;
begin
l_numOfWords := regexp_count(l_str, '[^,]+');
dbms_output.put('Number of words: ');
dbms_output.put_line(to_char(l_numOfWords));
end;
Result:
Number of words: 4

Get list of arguments with default value

I use ALL_ARGUMENTS to get list of arguments in oracle 10g, but I can't find out is there default value for argument. Haw can I do it?
you might need to resort to plsql programming in 10g, in the vein of the code sample below. this solution is certainly brute-force in some sense, as you basically write part of a function/procedure declaration parser using very low-level primitives. however, regular expression functions aren't available in 10g either ...
the gist of the code is:
iterate over the source code lines of procedure/function declarations
note the name of the most recently declared routine
analyze all lines containing the keyword 'DEFAULT', getting the argument name and the default value spec (this is not outlined in detail in the code sample).
beware of pitfalls:
multiline declarations
C-style comments containing C-style comment opening strings
( a la /* blah blah / blah blah **/ )
string literals containing keywords
hope that helps anyway, best regards.
the code:
DECLARE
l_arg_and_more VARCHAR2(400);
l_current_unit VARCHAR2(400);
l_default_spec VARCHAR2(400);
l_offset_default BINARY_INTEGER;
l_offset_eoname BINARY_INTEGER;
BEGIN
FOR i IN (
select text
from all_source
where owner = '<name of owner>'
and name = '<object name>'
and type in ( 'PACKAGE', 'PROCEDURE', 'FUNCTION')
and text not like '--%'
order by line
) LOOP
IF i.text LIKE '%FUNCTION%' OR i.text LIKE '%PROCEDURE%' THEN
IF i.text LIKE '%FUNCTION%' THEN
l_current_unit := LTRIM(SUBSTR(i.text, INSTR(i.text, 'FUNCTION') + LENGTH('FUNCTION')), ' ');
l_offset_eoname := INSTR(l_current_unit, ' ');
IF l_offset_eoname = 0 OR l_offset_eoname > INSTR(l_current_unit, '(') THEN
l_offset_eoname := INSTR(l_current_unit, '(');
END IF;
IF l_offset_eoname <> 0 THEN
l_current_unit := SUBSTR(l_current_unit, 1, l_offset_eoname-1);
ELSE
l_current_unit := 'unidentified';
END IF;
END IF;
END IF;
--
IF i.text LIKE '%DEFAULT%' THEN
l_offset_default := INSTR (i.text, 'DEFAULT');
l_arg_and_more := SUBSTR(i.text, 1, l_offset_default - 1);
l_default_spec := SUBSTR(i.text, l_offset_default + LENGTH('DEFAULT') + 1);
--
-- process l_arg_and_more to get the arg name, l_default_spec for the default value
--
END IF;
END LOOP;
END;
I know it is a bit too late but this may help. I'm facing similar problem at this time. Needs more testing...
SELECT SUBSTR(final_str, start_pos+1, (end_pos-start_pos-1) ) dflt_values, start_pos, end_pos
FROM
(
SELECT start_pos, final_end_pos end_pos, MOD(ROWNUM, 2) rno, final_str FROM
(
SELECT distinct start_pos, end_pos, final_str, (CASE WHEN end_pos < start_pos THEN (start_pos*2) ELSE end_pos END) final_end_pos
FROM
(
SELECT Instr(final_str, '=', LEVEL) start_pos, Instr(final_str, ',', LEVEL) end_pos, final_str --<<-- distinct
FROM
(
SELECT RTRIM(SUBSTR(str, 1, e_start), ',')||RTRIM(SUBSTR(str, e_start, e_end-e_start), ')') final_str
, e_start
, e_end
, e_end-e_start
FROM
(
SELECT str, Instr(str, ',', 1, 5) e_start, Instr(str, ')') e_end
FROM
(
SELECT REPLACE(REPLACE(REPLACE(SUBSTR(str, INSTR(str, '(')+1), chr(32), ''), chr(10), chr(32) ), 'DEFAULT', ':=') str
FROM
(
SELECT 'PROCEDURE Some_Proc(p_arg1 VARCHAR2:= ''Arg1''
, p_arg2 VARCHAR2:= ''Arg2''
, p_arg3 DATE DEFAULT ''SYSDATE-90''
, p_arg4 VARCHAR2 DEFAULT ''NULL''
, p_arg5 NUMBER := ''10'')' str
FROM dual
))))
CONNECT BY LEVEL <= LENGTH(final_str)
)
WHERE start_pos > 0
ORDER BY start_pos, end_pos
))
WHERE rno > 0
/
In the view SYS.ALL_ARGUMENTS there is a column named DEFAULT_VALUE, but even in 11g it's type is LONG and is a headache to work around and convert to CLOB.
The "easiest" (not the best) way is to create a table from this view, using TO_LOB to convert this field to a CLOB and work with that.

Resources