How to use If condition in function? - oracle19c

I am using oracle 19C on windows 10.
I want to create a function return the currency rate base on pass in ID as sample below:
CREATE OR REPLACE NONEDITIONABLE FUNCTION getexchangerate (
oid IN VARCHAR2
) RETURN VARCHAR2 IS
exchangeoid VARCHAR2(36 BYTE) := NULL;
currency VARCHAR2(10 BYTE);
exrate NUMBER(10, 4) := 0.0;
l_ratethb NUMBER(10, 2);
l_rateusd NUMBER(10, 2);
l_rateeur NUMBER(10, 2);
BEGIN
IF ( oid != NULL ) THEN
exchangeoid := regexp_substr(oid, '[^;]+', 1, 1);
currency := regexp_substr(oid, '[^;]+', 1, 2);
END
IF exchangeoid != NULL THEN
SELECT
oid,
ratethb,
rateusd,
rateeur
INTO
exchangeoid,
l_ratethb,
l_rateusd,
l_rateeur
FROM
mexchange
WHERE
oid = exchangeoid;
END
IF CURRENCY !=NULL THEN
IF CURRENCY = 'THB' THEN
exrate := L_RATETHB;
END
IF CURRENCY ='USD' THEN
exrate := L_RATEUSD;
END
IF CURRENCY = 'EUR' THEN
exrate := l_rateeur;
END
END
return exrate;
END getexchangerate;
Above function error:
Error(17,8): PLS-00103: Encountered the symbol "EXCHANGEOID" when expecting one of the following: ;
The pass in value is (534534554xxxxx;USD)
Firstly, need to split oid from currency
then, select the row at oid string.
finally, return the rate base on pass in currency.
I have no idea to complete this function. I am young in PL/SQL.
Can anyone correct or suggest to me, please?
Thank you in advance.

I had changed a bit something like this. It works.
IF CURRENCY IS NOT NULL THEN
IF CURRENCY = 'THB' THEN
exrate := L_RATETHB;
ELSIF CURRENCY ='USD' THEN
exrate := L_RATEUSD;
ELSE CURRENCY = 'EUR' THEN
exrate := l_rateeur;
END IF;
END IF;
Thank you very much for your all suggestion.

From the documentation:
The IF THEN statement has this structure:
IF condition THEN
statements
END IF;
You are getting that error because you finish all your IF blocks with END instead of END IF;. That means it sees this:
...
END
IF exchangeoid != NULL THEN
as END IF and then sees exchangeoid where it expects to see a semicolon completing it.
As #Aleksej pointed out, you are also not handling nulls properly.
So this compiles; see inline comments:
CREATE OR REPLACE NONEDITIONABLE FUNCTION getexchangerate (
oid IN VARCHAR2
) RETURN VARCHAR2 IS
exchangeoid VARCHAR2(36 BYTE) := NULL;
currency VARCHAR2(10 BYTE);
exrate NUMBER(10, 4) := 0.0;
l_ratethb NUMBER(10, 2);
l_rateusd NUMBER(10, 2);
l_rateeur NUMBER(10, 2);
BEGIN
IF ( oid IS NOT NULL ) THEN -- changed != NULL to IS NOT NULL
exchangeoid := regexp_substr(oid, '[^;]+', 1, 1);
currency := regexp_substr(oid, '[^;]+', 1, 2);
END IF; --- added IF;
IF exchangeoid IS NOT NULL THEN -- changed != NULL to IS NOT NULL
SELECT
oid,
ratethb,
rateusd,
rateeur
INTO
exchangeoid,
l_ratethb,
l_rateusd,
l_rateeur
FROM
mexchange
WHERE
oid = exchangeoid;
END IF; --- added IF;
IF CURRENCY IS NOT NULL THEN -- changed != NULL to IS NOT NULL
IF CURRENCY = 'THB' THEN
exrate := L_RATETHB;
END IF; --- added IF;
IF CURRENCY ='USD' THEN
exrate := L_RATEUSD;
END IF; --- added IF;
IF CURRENCY = 'EUR' THEN
exrate := l_rateeur;
END IF; --- added IF;
END IF; --- added IF;
return exrate;
END getexchangerate;
/
You could replace the three currency comparison IF ... END IF; blocks with IF ... ELSIF ... ELSIF ... END IF;, or a CASE statement. You can read about those options in the documentation too.

Related

How can I optimize my code so that I dont duplicate it

I'm trying to create a procedure that puts "-" between different dates and "0" if the is single digit, but i'm having a very hard time not duplicating my code.
procedure put (Date : in Date_Type) is
begin
Put(Date.Y, Width => 1);
Put("-");
if Date.M <= 9 then
Put("0");
end if;
Put(Date.M, Width => 1);
Put("-");
if Date.D <= 9 then
Put("0");
end if;
Put(Date.D, Width => 1);
end put;
This is the best solution I came up with
An example of a nested procedure is:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
procedure Main is
subtype Year_Num is Integer range 1_900 .. 2_040;
subtype Month_Num is Integer range 1 .. 12;
subtype Day_Num is Integer range 1 .. 31;
type Date_Type is record
Y : Year_Num;
M : Month_Num;
D : Day_Num;
end record;
procedure Put (Date : Date_Type) is
procedure zerofill (Val : in Integer) is
begin
Put ("-" & (if (Val < 10) then "0" else ""));
Put (Item => Val, Width => 0);
end zerofill;
begin
Put (Item => Date.Y, Width => 0);
zerofill (Date.M);
zerofill (Date.D);
end Put;
A_Date : Date_Type := (2022, 12, 8);
begin
Put (A_Date);
end Main;
The nested nature of this answer is because the zerofill procedure is defined within the put procedure.
Came to this solution, I didnt duplicate my code but I somehow feel like I made it more complicated
procedure Zero(item : in integer) is
begin
Put("-");
if item < 10 then
Put('0');
end if;
Put(Item,Width =>0);
end Zero;
procedure put (Date : in Date_Type) is
begin
Put(Date.Y, Width => 0);
Zero(Date.M);
Zero(Date.D);
end put;

Oracle - Optimizing looping over all regex matches on CLOB column when only interested in the distinct matches

I'm making a stored procedure that loops over a table (with many thousand rows), and for each row there is a CLOB column from which I want to fetch all matches on a regular expression (sa "FNR"). Thereafter, I want to insert each distinct match in a new table. A single CLOB column may contain thousands of matches, but oftentimes it is the same "FNR" that repeats itself in the CLOB - i.e. there are much fewer distinct regex matches, and those are the only ones I'm interested in.
However, the procedure I've made takes ridiculously long time, and I suspect looping over all the matches is the most time consuming part.
My procedure looks like this:
CREATE OR REPLACE PROCEDURE SP_MTV_FINN_FNR AS
BEGIN
DECLARE
v_n NUMBER;
v_cnt NUMBER;
v_mtrid NUMBER;
v_regex_fnr VARCHAR2(54) := '(((0[1-9]|[12]\d|3[01])(0[1-9]|1[012])(\d{2}))(\d{5}))';
v_doc CLOB;
v_fnr VARCHAR2(11);
BEGIN
-- Get all rows from table --
SELECT COUNT(*) INTO v_n FROM TABLE;
IF v_n > 0 THEN
-- Loop over all rows --
FOR i IN 1..v_n LOOP
SELECT doc, mtrid
INTO v_doc, v_mtrid
FROM (SELECT DOC doc, ID mtrid, ROWNUM rnum
FROM TABLE
WHERE ROWNUM <=i)
WHERE rnum >= i;
IF v_doc IS NOT NULL THEN
SELECT REGEXP_COUNT(v_doc, v_regex_fnr) INTO v_cnt FROM DUAL;
IF v_cnt >= 1 THEN
-- For each regex match - time consuming, right? --
FOR j IN 1..v_cnt LOOP
SELECT REGEXP_SUBSTR(v_doc, v_regex_fnr, 1, j, 'm') INTO v_fnr FROM DUAL;
IF CHECK_FNR(v_fnr) = 'TRUE' THEN
INSERT INTO TABLE2(MTR_ID, FNR)
SELECT v_mtrid, v_fnr FROM DUAL;
END IF;
END LOOP;
END IF;
END IF;
COMMIT;
END LOOP;
END IF;
END LOOP;
EXCEPTION WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Error - rollback');
DBMS_OUTPUT.PUT_LINE('The error code is ' || SQLCODE || '- ' || SQLERRM);
ROLLBACK;
END;
/
Do anybody have an idea of how to optimize this procedure?
I'm using Oracle 11.2.0.3.0.
(Btw, I know of the ctx_entity-package, but it is disabled on this version. Still, I'm thinking of enabling it.)
UPDATE
After applying the very helpful performance optimizing techniques given by nop77svk, I can with certainty say that the regexp_substr() over a CLOB is the bottleneck, as there unfortunately were no performance improvement.
However, I came up with a "hack/workaround" where I minimize the amount of regexp_substr() calls, with a tremendous performance improvement. First I thought of making an incrementally "trained" regex, excluding previous matches, but as Oracle doesn't support negative lookahead, this didn't work. I ended up with saving the CLOB, and using regexp_replace() to remove all occurences of the match. As there were a lot of the same occurences in the CLOB, this saved the procedure from a lot of regexp_substr() calls, and it simultaneously dealt with the distinct requirement.
Under follows my result, based on nop77svk's contribution. And yes, I'm back to using DUAL in the MERGE-statement, but is there any way around it here?
CREATE OR REPLACE PROCEDURE SP_MTV_FINN_FNR2 AS
BEGIN
DECLARE
v_regex_fnr VARCHAR2(54) := '(((0[1-9]|[12]\d|3[01])(0[1-9]|1[012])(\d{2}))(\d{5}))';
v_fnr VARCHAR2(11);
v_doc CLOB;
type rec_table2 is record (
mtr_id table2.mtr_id%type,
fnr table2.fnr%type
);
type arr_table2 is table of rec_table2 index by simple_integer;
table2_bulk arr_table2;
table2_row rec_table2;
BEGIN
FOR rec IN (
select doc, MTR_ID as mtrid
from TABLE
where DOC is not null
) LOOP
v_doc := rec.doc;
loop
v_fnr := REGEXP_SUBSTR(v_doc, v_regex_fnr, 1, 1, 'm');
exit when v_fnr is null;
v_vedlegg := REGEXP_REPLACE(v_doc, v_fnr , '' , 1 , 0); -- Incrementally remove all occurences of match from doc --
IF CHECK_FNR(v_fnr) = 'TRUE' THEN
table2_row.mtr_id := rec.mtrid;
table2_row.fnr := v_fnr;
table2_bulk(table2_bulk.count+1) := table2_row;
END IF;
END LOOP;
END LOOP;
forall i in indices of table2_bulk
MERGE INTO TABLE2 T
USING (SELECT table2_bulk(i).mtr_id mtrid, table2_bulk(i).fnr fnr FROM DUAL) B
ON (T.MTR_ID = B.mtrid AND T.FNR = B.fnr)
WHEN NOT MATCHED THEN INSERT (T.MTR_ID, T.FNR)
VALUES (B.mtrid, B.fnr);
COMMIT;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Error - rollback');
DBMS_OUTPUT.PUT_LINE('The error code is ' || SQLCODE || '- ' || SQLERRM);
ROLLBACK;
END;
END;
/
Iteratively tuning your PL/SQL block ...
Iteration 0: Fixing syntax errors ...
CREATE OR REPLACE PROCEDURE SP_MTV_FINN_FNR AS
v_n NUMBER;
v_cnt NUMBER;
v_mtrid NUMBER;
v_regex_fnr VARCHAR2(54) := '(((0[1-9]|[12]\d|3[01])(0[1-9]|1[012])(\d{2}))(\d{5}))';
v_doc CLOB;
v_fnr VARCHAR2(11);
BEGIN
-- Get all rows from table --
SELECT COUNT(*) INTO v_n FROM TABLE;
IF v_n > 0 THEN
-- Loop over all rows --
FOR i IN 1..v_n LOOP
SELECT doc, mtrid
INTO v_doc, v_mtrid
FROM (SELECT DOC doc, ID mtrid, ROWNUM rnum
FROM TABLE
WHERE ROWNUM <=i)
WHERE rnum >= i;
IF v_doc IS NOT NULL THEN
SELECT REGEXP_COUNT(v_doc, v_regex_fnr) INTO v_cnt FROM DUAL;
IF v_cnt >= 1 THEN
-- For each regex match - time consuming, right? --
FOR j IN 1..v_cnt LOOP
SELECT REGEXP_SUBSTR(v_doc, v_regex_fnr, 1, j, 'm') INTO v_fnr FROM DUAL;
IF CHECK_FNR(v_fnr) = 'TRUE' THEN
INSERT INTO TABLE2(MTR_ID, FNR)
SELECT v_mtrid, v_fnr FROM DUAL;
END IF;
END LOOP;
END IF;
END IF;
COMMIT;
END LOOP;
END IF;
EXCEPTION WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Error - rollback');
DBMS_OUTPUT.PUT_LINE('The error code is ' || SQLCODE || '- ' || SQLERRM);
ROLLBACK;
END;
Iteration 1: Removing unnecessary context switches and useless row counting ...
DECLARE
v_cnt NUMBER;
v_regex_fnr VARCHAR2(54) := '(((0[1-9]|[12]\d|3[01])(0[1-9]|1[012])(\d{2}))(\d{5}))';
v_fnr VARCHAR2(11);
BEGIN
FOR rec IN (
select doc, id as mtrid
from table
) LOOP
IF rec.doc IS NOT NULL THEN
v_cnt := REGEXP_COUNT(rec.doc, v_regex_fnr);
IF v_cnt >= 1 THEN
-- For each regex match - time consuming, right? --
FOR j IN 1..v_cnt LOOP
v_fnr := REGEXP_SUBSTR(rec.doc, v_regex_fnr, 1, j, 'm');
IF CHECK_FNR(v_fnr) = 'TRUE' THEN
INSERT INTO TABLE2(MTR_ID, FNR) values (rec.mtrid, v_fnr);
END IF;
END LOOP;
END IF;
END IF;
END LOOP;
COMMIT;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Error - rollback');
DBMS_OUTPUT.PUT_LINE('The error code is ' || SQLCODE || '- ' || SQLERRM);
ROLLBACK;
END;
/
Iteration 2: Decreasing number of outer loops ...
DECLARE
v_cnt NUMBER;
v_regex_fnr VARCHAR2(54) := '(((0[1-9]|[12]\d|3[01])(0[1-9]|1[012])(\d{2}))(\d{5}))';
v_fnr VARCHAR2(11);
BEGIN
FOR rec IN (
select doc, id as mtrid
from table
where doc is not null
) LOOP
v_cnt := REGEXP_COUNT(rec.doc, v_regex_fnr);
IF v_cnt >= 1 THEN
-- For each regex match - time consuming, right? --
FOR j IN 1..v_cnt LOOP
v_fnr := REGEXP_SUBSTR(rec.doc, v_regex_fnr, 1, j, 'm');
IF CHECK_FNR(v_fnr) = 'TRUE' THEN
INSERT INTO TABLE2(MTR_ID, FNR) values (rec.mtrid, v_fnr);
END IF;
END LOOP;
END IF;
END LOOP;
COMMIT;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Error - rollback');
DBMS_OUTPUT.PUT_LINE('The error code is ' || SQLCODE || '- ' || SQLERRM);
ROLLBACK;
END;
/
Iteration 3: Shortening the code of iteration 2 ...
DECLARE
v_regex_fnr VARCHAR2(54) := '(((0[1-9]|[12]\d|3[01])(0[1-9]|1[012])(\d{2}))(\d{5}))';
v_fnr VARCHAR2(11);
BEGIN
FOR rec IN (
select doc, id as mtrid, REGEXP_COUNT(rec.doc, v_regex_fnr) as regexp_cnt
from table
where doc is not null
and regexp_like(doc, v_regex_fnt)
) LOOP
FOR j IN 1..rec.regexp_cnt LOOP
v_fnr := REGEXP_SUBSTR(rec.doc, v_regex_fnr, 1, j, 'm');
IF CHECK_FNR(v_fnr) = 'TRUE' THEN
INSERT INTO TABLE2(MTR_ID, FNR) values (rec.mtrid, v_fnr);
END IF;
END LOOP;
END LOOP;
COMMIT;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Error - rollback');
DBMS_OUTPUT.PUT_LINE('The error code is ' || SQLCODE || '- ' || SQLERRM);
ROLLBACK;
END;
/
Iteration 4: Removing unnecessary regexp_count() counting ...
DECLARE
v_regex_fnr VARCHAR2(54) := '(((0[1-9]|[12]\d|3[01])(0[1-9]|1[012])(\d{2}))(\d{5}))';
v_fnr VARCHAR2(11);
j integer;
BEGIN
FOR rec IN (
select doc, id as mtrid
from table
where doc is not null
) LOOP
j := 1;
loop
v_fnr := REGEXP_SUBSTR(rec.doc, v_regex_fnr, 1, j, 'm');
exit when v_fnt is null;
IF CHECK_FNR(v_fnr) = 'TRUE' THEN
INSERT INTO TABLE2(MTR_ID, FNR) values (rec.mtrid, v_fnr);
END IF;
j := j + 1;
END LOOP;
END LOOP;
COMMIT;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Error - rollback');
DBMS_OUTPUT.PUT_LINE('The error code is ' || SQLCODE || '- ' || SQLERRM);
ROLLBACK;
END;
/
Iteration 5: Saving the results to memory and flushing it to DB at once (using the collection binding), plus dealing with the distinct requirement ...
create or replace type obj_table2
as
object (
mtr_id integer,
fnr varchar2(4000)
);
/
create or replace type arr_table2
as
table of obj_table2;
/
DECLARE
v_regex_fnr VARCHAR2(54) := '(((0[1-9]|[12]\d|3[01])(0[1-9]|1[012])(\d{2}))(\d{5}))';
v_fnr VARCHAR2(11);
j integer;
table2_bulk arr_table2 := arr_table2();
BEGIN
FOR rec IN (
select doc, id as mtrid
from table
where doc is not null
) LOOP
j := 1;
loop
v_fnr := REGEXP_SUBSTR(rec.doc, v_regex_fnr, 1, j, 'm');
exit when v_fnt is null;
IF CHECK_FNR(v_fnr) = 'TRUE' THEN
table2_bulk.extend();
table2_bulk(table2_bulk.last) := new obj_table2(
mtr_id => rec.mtrid,
fnr => v_fnr
);
END IF;
j := j + 1;
END LOOP;
END LOOP;
insert into table2(mtr_id, fnr)
select mtr_id, fnr
from table(table2_bulk) X
minus
select mtr_id, fnr
from table2;
COMMIT;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Error - rollback');
DBMS_OUTPUT.PUT_LINE('The error code is ' || SQLCODE || '- ' || SQLERRM);
ROLLBACK;
END;
/
Iteration 6: Throwing it all away whilst having decided to show off detestably ...
insert into table2 (mtr_id, fnr)
with xyz (doc, mtrid, fnr, j) as (
select doc, id as mtrid, cast(null as varchar2(4000)) as fnr, 0 as j
from table A
where doc is not null
--
union all
--
select doc, mtrid,
regexp_substr(doc, '(((0[1-9]|[12]\d|3[01])(0[1-9]|1[012])(\d{2}))(\d{5}))', 1, j+1, 'm') as fnr,
j+1
from xyz X
where j = 0
or j > 0 and X.fnr is not null
)
select distinct mtrid, fnr
from xyz
where j > 0
and fnr is not null
and CHECK_FNR(fnr) = 'TRUE'
;
commit;
Please note that these code snippets may even not work. Since you did not provide us with any test data setup, we can tune your code only in a hypothetical way.
Please note that the slowest part of this is still the regexp_substr() over a CLOB value. You might want to think about using the position parameter of regexp_substr() instead of the occurence parameter to get the subsequent regexp matches.
Enjoy.
Removing the DUAL references with static calls. Thus saving the unwanted context switching between PL/SQL and SQL Engine.
I also added implicit cursor to process record by record.
Next level of improvement could be bulk insert into the other table. Though I didn't do it here.
CREATE OR REPLACE PROCEDURE SP_MTV_FINN_FNR AS
BEGIN
DECLARE
v_n NUMBER;
v_cnt NUMBER;
v_mtrid NUMBER;
v_regex_fnr VARCHAR2(54) := '(((0[1-9]|[12]\d|3[01])(0[1-9]|1[012])(\d{2}))(\d{5}))';
v_doc CLOB;
v_fnr VARCHAR2(11);
BEGIN
-- Get all rows from table --
/* Lets go with a Implicit cursor */
FOR MYREC IN (SELECT DOC doc, ID mtrid
FROM TABLE)
LOOP
IF MYREC.DOC IS NOT NULL THEN
v_cnt := REGEXP_COUNT(MYREC.DOC, v_regex_fnr);
IF v_cnt >= 1 THEN
-- For each regex match - time consuming, right? --
FOR j IN 1..v_cnt LOOP
v_fnr := REGEXP_SUBSTR(MYREC.DOC, v_regex_fnr, 1, j, 'm');
IF CHECK_FNR(v_fnr) = 'TRUE' THEN
INSERT INTO TABLE2(MTR_ID, FNR)
VALUES (MYREC.MTRID,v_fnr);
END IF;
END LOOP;
END IF;
COMMIT;
END IF;
END LOOP;
EXCEPTION WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Error - rollback');
DBMS_OUTPUT.PUT_LINE('The error code is ' || SQLCODE || '- ' || SQLERRM);
ROLLBACK;
END;

get last number part of a String in PL/SQL

In PL/SQL, is there any way to calculate next serial number from another one like 'A1B0010C'. Next serial no will be A1B0011C (+1). My idea is retrieve number part, increase it get return string back.
I can do this in java, but passing more than 1000 elements to Oracle will cause problems in IN clause.
So please help, any suggestion is appreciated.
Try to write some recursive function like this
This Function returns next character. ex: D after C. 0 after Z.
create or replace function get_next_character(ch char)
return char
is
result_ch char(1) := ch;
begin
IF UPPER(ch) = 'Z' or ch = '9' THEN
result_ch := 'A';
ELSE
result_ch := chr(ascii(ch) + 1);
END IF;
return upper(result_ch);
end get_next_character;
and this is your actual function which returns the next serial number
So, it generates 'A1B0010D' if your input is 'A1B0010C'
create or replace function get_next_serial(p_serial IN varchar2) -- PASS THE REQUIRED ARGUMENTS
return varchar2
IS
v_ret_serial varchar2(100);
v_in_serial varchar2(100) := p_serial;
tmp varchar2(100);
last_char char(1);
begin
tmp := v_in_serial;
for i in reverse 1..length(v_in_serial) loop
last_char := substr(tmp, length(tmp));
last_char := get_next_character(last_char);
tmp := substr(v_in_serial, 1, length(tmp)-1);
v_in_serial := substr(v_in_serial, 1, i-1) || last_char || substr(v_in_serial, i+1);
IF last_char <> 'A' then
exit;
END IF;
end loop;
IF last_char = 'A' THEN
v_in_serial:= 'A'||v_in_serial;
END IF;
return UPPER(v_in_serial);
exception
when NO_DATA_FOUND then
return 'AA';
when others then
return null;
end get_next_serial;
you can call this function (get_next_serial('abc')) where ever you want;
select get_next_serial('ALK0989KJ') from dual
You can place these two functions in a package and use at your convenience.
You can achieve it by using the following mix of Regexp_* functions:
SQL> with t1 as(
2 select 'A1B0010C' col from dual
3 )
4 select regexp_replace(col, '[[:digit:]]+'
5 , to_char(to_number(regexp_substr(col, '[[:digit:]]+',1,2) + 1), 'fm0000')
6 , 1
7 , 2
8 ) as Res
9 from t1
10 ;
RES
------------
A1B0011C
UPDATE In response to the comment.
SQL> with t1 as(
2 select 'A1B0010C' col from dual union all
3 select 'A1B0010C2' col from dual union all
4 select 'A1B0012C001' col from dual union all
5 select 'S000001' col from dual
6 )
7 select col
8 , regexp_replace(col
9 , '([[:digit:]]+)([[:alpha:]]+$|$)'
10 , LPad(to_char(to_number(num) + 1), length(num), '0') || '\2'
11 ) as Res
12 from (select col
13 , regexp_substr(col, '([[:digit:]]+)([[:alpha:]]+$|$)', 1, 1, 'i', 1) as num
14 from t1
15 )
16 ;
COL RES
----------- -----------------
A1B0010C A1B0011C
A1B0010C2 A1B0010C3
A1B0012C001 A1B0012C002
S000001 S000002
create or replace function nextSerial(s in varchar2) return varchar2 as
i integer;
done boolean := false;
c char;
newserial varchar2(4000);
begin
newserial := s;
i := length(newserial);
while i>0 and not done loop
c := substr(newserial, i, 1);
if c='9' then
newserial := substr(newserial, 1, i-1) || '0' || substr(newserial, i+1);
elsif c>='0' and c<='8' then
c := chr(ascii(c)+1);
newserial := substr(newserial, 1, i-1) || c || substr(newserial, i+1);
done := true;
end if;
i := i-1;
end loop;
if not done then
newserial := '1' || newserial;
end if;
return newserial;
end;

Does Oracle provide the way of returning multiple substrings from a string/clob row that has been parsed?

I know that there are REGEXP_ functions, but this ones return maximum 1 row when simply applied to a string var. I know that you can use it in a WHERE clause, but I need a way of dealing with large strings/text/clob vars not tables, so I would like to know if some function can return multiple substrings somehow (I am thinking at something like the explode() or - even better - preg_match() in PHP).
As APC suggested I am providing a sample string and examples of outcomes that I would like to get..
Like I said in the comments bellow, I wand to get the functions/procedures bodies (functions/procedures that are part of some packages) like this:
THE STRING:
create or replace PACKAGE BODY export_db IS
FUNCTION o_functie(ceva NUMBER) return boolean IS
BEGIN
RETURN null;
END;
FUNCTION o_functie(ceva NUMBER, altceva VARCHAR2) return boolean IS
BEGIN
RETURN null;
END;
PROCEDURE export_db_tabele IS
v_ddl CLOB;
BEGIN
FOR c IN(SELECT object_type,object_name FROM user_objects where object_type IN ( 'TABLE')) LOOP
v_ddl := v_ddl || dbms_metadata.get_ddl(c.object_type, c.object_name)||';'||CHR(13)||CHR(10);
END LOOP;
INSERT INTO dbexport(tipobiect, ddltext) VALUES ('tabele', v_ddl);
END;
PROCEDURE export_db_restrictii IS
v_ddl CLOB;
BEGIN
FOR c IN(SELECT constraint_name FROM user_constraints) LOOP
v_ddl := v_ddl || dbms_metadata.get_ddl('CONSTRAINT', c.constraint_name)||';'||CHR(13)||CHR(10);
END LOOP;
INSERT INTO dbexport(tipobiect, ddltext) VALUES ('restrictii', v_ddl);
END;
PROCEDURE export_db_secvente IS
v_ddl CLOB;
BEGIN
FOR c IN(SELECT sequence_name FROM user_sequences) LOOP
v_ddl := v_ddl || dbms_metadata.get_ddl('SEQUENCE', c.sequence_name)||';'||CHR(13)||CHR(10);
END LOOP;
INSERT INTO dbexport(tipobiect, ddltext) VALUES ('secvente', v_ddl);
END;
PROCEDURE export_db_proceduri IS
v_ddl CLOB;
BEGIN
FOR c IN(SELECT OBJECT_NAME FROM user_objects up WHERE object_type = 'PROCEDURE') LOOP
v_ddl := v_ddl || dbms_metadata.get_ddl('PROCEDURE', c.OBJECT_NAME)||CHR(13)||CHR(10);
END LOOP;
INSERT INTO dbexport(tipobiect, ddltext) VALUES ('proceduri', v_ddl);
END;
PROCEDURE export_db_functii IS
v_ddl CLOB;
BEGIN
FOR c IN(SELECT OBJECT_NAME FROM user_objects uo WHERE object_type = 'FUNCTION' ) LOOP
v_ddl := v_ddl || dbms_metadata.get_ddl('FUNCTION', c.OBJECT_NAME)||CHR(13)||CHR(10);
END LOOP;
INSERT INTO dbexport(tipobiect, ddltext) VALUES ('functii', v_ddl);
END;
PROCEDURE export_db_pachete IS
v_ddl CLOB;
BEGIN
FOR c IN(SELECT OBJECT_NAME FROM user_objects uo WHERE object_type = 'PACKAGE' ) LOOP
v_ddl := v_ddl || dbms_metadata.get_ddl('PACKAGE', c.OBJECT_NAME)||CHR(13)||CHR(10);
END LOOP;
INSERT INTO dbexport(tipobiect, ddltext) VALUES ('pachete', v_ddl);
END;
PROCEDURE export_db_declansatoare IS
v_ddl CLOB;
BEGIN
FOR c IN(SELECT OBJECT_NAME FROM user_objects uo WHERE object_type = 'TRIGGER' ) LOOP
v_ddl := v_ddl || dbms_metadata.get_ddl('TRIGGER', c.OBJECT_NAME)||CHR(13)||CHR(10);
END LOOP;
INSERT INTO dbexport(tipobiect, ddltext) VALUES ('declansatoare', v_ddl);
END;
END;
OUTCOMES would be:
ex:
FUNCTION o_functie(ceva NUMBER, altceva VARCHAR2) return boolean IS
BEGIN
RETURN null;
END;
and
PROCEDURE export_db_secvente IS
v_ddl CLOB;
BEGIN
FOR c IN(SELECT sequence_name FROM user_sequences) LOOP
v_ddl := v_ddl || dbms_metadata.get_ddl('SEQUENCE', c.sequence_name)||';'||CHR(13)||CHR(10);
END LOOP;
INSERT INTO dbexport(tipobiect, ddltext) VALUES ('secvente', v_ddl);
END;
IF you know any other method of geting those procedures/functions I am glad to give up parsing all this - from what I know - there is no select to do that... not even from user_source, user_procedures tables or other...
Something like this maybe:
CREATE OR REPLACE FUNCTION explode(longline varchar)
RETURN sys.dbms_debug_vc2coll PIPELINED
IS
pos PLS_INTEGER;
lastpos PLS_INTEGER;
element varchar(2000);
BEGIN
lastpos := 1;
pos := instr(longline, ',');
while pos > 0 loop
element := substr(longline, lastpos, pos - lastpos);
lastpos := pos + 1;
pos := instr(longline, ',', lastpos);
pipe row(element);
end loop;
if lastpos <= length(longline) then
pipe row (substr(longline, lastpos));
end if;
RETURN;
END;
/
This can be used like this:
SQL> select * from table(explode('1,2,3'));
COLUMN_VALUE
---------------------------------------------
1
2
3
SQL>
If you are not on 11.x you need to define the return type yourself:
create type char_table as table of varchar(4000);
and change the function declaration to:
CREATE OR REPLACE FUNCTION explode(longline varchar)
RETURN char_table pipelined
.....

apex_application.g_notification

I'm trying to do a notification message when doing a process on submit but is not working
DECLARE
l_sendto NUMBER := :P22_SEND_TO;
l_user_groups NUMBER := :P22_USER_GROUP;
l_media NUMBER := :P22_MEDIA;
l_usersessionid NUMBER := :APP_SESSION;
SINGLE_USER CONSTANT number :=0;
l_aux NUMBER;
e EXCEPTION;
BEGIN
--verify if user
select userid into l_aux FROM SURV_TEMP_SENDTO WHERE userid = l_user_groups and usersessionid= l_usersessionid and rownum =1;
IF l_aux > 0 THEN
RAISE e;
ELSE
IF l_sendto = SINGLE_USER THEN
--if selected all types of medias of one user
INSERT INTO SURV_TEMP_SENDTO (userid, mediatypeid, usersessionid)
(SELECT zm.userid, zm.mediatypeid, l_usersessionid
FROM z.media zm, Z.media_type zmt
WHERE zmt.mediatypeid = zm.mediatypeid
AND zm.userid = l_user_groups);
apex_application.g_print_success_message := 'Inserting OK';
END IF;
EXCEPTION
WHEN e THEN
apex_application.g_global_notification:= 'Error inserting values';
--apex_application.g_print_success_message := 'Error inserting values';
END;
In the raise EXCEPTION it doesn't show any msg but if he enters the ELSE it writes the success msg.
Anyone know why it doesn't work in EXCEPTION?
In the ELSE you are using g_print_success_message. That will work in the EXCEPTION section too. When I tried it, g_global_notification didn't work in either main body or exception section, but only appeared if an exception was raised and not handled - I don't know why though.