get last number part of a String in PL/SQL - regex

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;

Related

How to use If condition in function?

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.

Pascal. Case instead If

I m just trying to write my second (yes, it s absolutely new for me) teaching programm on Pascal. I ve made once, using "if", here it is:
program DemoBool;
Var A: Boolean ; B: Boolean ;
C:Integer ; D:Integer ;
I:Integer;
begin
write('Enter A(1/0): '); readln(I);
if (I= 1)
then A:=true else A:=false;
writeln(A);
write('Enter B(1/0): '); readln(I);
if I=1
then B:=true else B:=false;
writeln(B);
write('enter C: '); readln(C);
write('enter D: '); readln(D);
IF ((A=B) AND (C>D)=TRUE OR NOT A )
THEN IF ((TRUE<>A) AND (C-D<3))
THEN writeln('a=b, c>d, or not A=true, (true<>a and c-d<3)=true')
ELSE writeln('a=b, c>d, or not A=true, (true<>a and c-d<3)=false')
ELSE writeln('a<>b, c<d, or not A=false') ;
readln;
end.
And how can I use case instead if for latest conditions?..
Can I write new Var-s , F, F2- Boolean and then somehow, making this:
F:= ((A=B) AND (C>D)=TRUE OR NOT A ) ;
F2:= ((TRUE<>A) AND (C-D<3));
use Case?
It s really not easy for me, but hope, I can manage this task) sorry for my explanation. Thank you for attention
There are several improvments possible to your use of boolean, but this is normal since you learn.
1
if (I= 1) then A:=true else A:=false;
You can directly assign to A the comparison result of I equal to 1
A := (I = 1);
Which leads to better byte code, even without optimization (a TEST than a SETZ instruction). The Paren pair is just for readability.
2
IF ((A=B) AND (C>D)=TRUE OR NOT A )
You don't need to compare the result of boolean operation to true of false.
if ((a=b) and (c>d) or not a)
The expression statements (a=b) and (c>d) already evaluates to a Boolean.
3
If you want to use the case of expression, let's say to replace the end of the program:
case (A = B) and (C > D)) and A and (C - D < 3) of
true: writeln('yep');
false: writeln('nope');
end;
note that I use the simplified statements of lurker comment.
As far as I understood you insisted on using a case statement. Though I entirely agree with #lurker and suppose that case statement is not useful here I have the following proposal.
program DemoBool;
var
BitLogic: byte;
tmpb: byte;
A: Boolean;
B: Boolean;
C: Integer;
D: Integer;
I: Integer;
function GetBoolBit(const B: Boolean; Offset: byte): byte;
begin
if B then
begin
result := 1;
result := result shl Offset;
end
else
result := 0;
end;
begin
//input the values
BitLogic := GetBoolBit(A = B, 0);
tmpb := GetBoolBit(A, 1);
BitLogic := tmpb or BitLogic;//make comparison and store the result
// in bit-by-bit mode - every new boolean with the new offset
// the code is unscrolled here. We can optimize it writing the following instead:
// BitLogic := GetBoolBit(A, 1) or BitLogic;
tmpb := GetBoolBit(C > D, 2);
BitLogic := tmpb or BitLogic;
tmpb := GetBoolBit(C - D < 3, 3);
BitLogic := tmpb or BitLogic;
// we get the following the lower bit will store the first boolean - A=B
// the second - the second - A
// the third - C>D etc.
// the fourth - C-D<3 etc.
//now we can make an adequate case statement:
case BitLogic of
0: {00000000b}writeln('all false');
1: {00000001b}writeln('A=B');
2: {00000010b}writeln('A(=true)');
3: {00000011b}writeln('A and A=B');
4: {00000100b}writeln('C>D');
5: {00000101b}writeln('C>D and A=B');
6: {00000110b}writeln('C>D and A');
7: {00000111b}writeln('C>D and A and A=B');
8: {00001000b}writeln('C - D < 3');
9: {00001001b}writeln('C - D < 3 and A=B');
10:{00001010b}writeln('C - D < 3 and A');
11:{00001011b}writeln('C - D < 3 and A and A=B');
12:{00001100b}writeln('C - D < 3 and C>D');
13:{00001101b}writeln('C - D < 3 and C>D and A=B');
14:{00001110b}writeln('C - D < 3 and C>D and A');
15:{00001111b}writeln('C - D < 3 and C>D and A and A=B');
end;
end.
P.S. Check the logics of your if then else statements. There is a false decision: you decide that if not C > D then C < D. It is C <= D. I would advise you to read this SO answer as well.

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;

Why this simple regexp doesn't work when CLOB size > 4KB?

declare
c clob := '0123456789ABCDEF';
begin
c := c||c||c||c; c := c||c||c||c;
c := c||c||c||c; c := c||c||c||c;
-- Now length(c) = 4096
c := regexp_substr(c, '(.*)\1'); --ERROR:No more data to read from socket. Why?
end;
Is it a bug?
It works fine when using varchar2 instead of clob.
I'm reasonably convinced this is a bug in Oracle, and is related to the specific regular expression used. In this SQLFiddle I played around with CLOBs of varying length, returning strings of varying length, and the only regular expression I found (in my very much non-exhaustive test) was the one supplied by OP. The code from the SQLFiddle is:
create table temp_output(s varchar2(4000));
declare
c16 clob := '0123456789ABCDEF';
c64 clob;
c256 clob;
c1k clob;
c4k clob;
c4kp2 clob; -- 4096 with 'x' at each end
c4kwx clob; -- 4095 with 'x' at each end
c16k clob;
c16kwx clob; -- 16k with x's embedded
x clob := 'x';
d clob;
begin
c64 := c16 || c16 || c16 || c16;
c256 := c64 || c64 || c64 || c64;
c1k := c256 || c256 || c256 || c256;
c4k := c1k || c1k || c1k || c1k;
c4kp2 := x || c4k || x;
c4kwx := x || SUBSTR(c4k, 1, 4093) || x; -- 4095 chars total
c16k := c4k || c4k || c4k || c4k;
c16kwx := c4k || x || c4k || c4k || x || c4k;
insert into temp_output(s) values ('LENGTH(c4k)=' || LENGTH(c4k));
insert into temp_output(s) values ('LENGTH(c16k)=' || LENGTH(c16k));
-- d := regexp_substr(c4kp2, '(.*)\1'); -- ERROR
-- d := regexp_substr(c4kp2, 'x.*x'); -- No error
d := regexp_substr(c16kwx, 'x.*x'); -- No error
insert into temp_output (s) values ('LENGTH(d)=' || LENGTH(d));
insert into temp_output (s) values (SUBSTR(d, 1, 4000));
end;
/
select * from temp_output
/
My original thinking was that perhaps there was an unpublished limit to the amount of text which could be extracted by the regexp, but that did not turn out to be correct.
Share and enjoy.

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
.....