Extracting values from a pipe separated string - sql

I have a string which looks like --
12361_BBMS_GTECHL|12362_BBMS_PRIM|12363_BBMS_SEC|....and so on
So i need to fetch
12361 and BBMS_GTECHL
12362 and BBMS_PRIM
12363 and BBMS_SEC
i used --
select *
FROM
TABLE(XMLSEQUENCE(
EXTRACT(
XMLTYPE('<rowset><row><Code>'||
replace(replace('12361=BBMS_GTECHL|12362=BBMS_PRIM','|','</Value></row><row><Code>'),'=','</Code><Value>')||'</Value>'||'</row></rowset>'),'/rowset/row')));
declare
l_val varchar2(1000);
begin
select substr('12361_BBMS_GTECHL|12362_BBMS_PRIM', instr('|')+1) into l_val from dual;
dbms_output.put_line(l_val);
end;
But getting problem in getting desired result !
I have need to write this logic in a package that i will do if i got some hint here.
My DB version is --
Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production

Here is a solution using a recursive factored subquery ("recursive CTE"). Note the use of pointers to the location of pipe symbols and the first underscore after each pipe (disregarding the other underscores). Also, the solution uses only standard INSTR and SUBSTR, avoiding the use of regular expressions (which perform somewhat slower - important if you process lots of data).
with input_data (input_str) as (
select '12361_BBMS_GTECHL|12362_BBMS_PRIM|12363_BBMS_SEC' from dual
),
t (str) as (
select '|' || input_str || '|' from input_data
),
r (lvl, code, descr, str, p1_from, p2_from, p1_to, p2_to) as (
select 0, null, null, str, 1, 1, instr(str, '_', 1, 1), instr(str, '|', 1, 2)
from t
union all
select lvl+1, substr(str, p2_from + 1, p1_to - p2_from - 1),
substr(str, p1_to + 1, p2_to - p1_to - 1),
str, p1_to, p2_to, instr(str, '_', p2_to + 1, 1),
instr(str, '|', p2_to + 1, 1)
from r
where p1_to != 0
)
select code, descr
from r
where lvl != 0;
Output:
CODE DESCR
------- --------------------
12361 BBMS_GTECHL
12362 BBMS_PRIM
12363 BBMS_SEC

If I were you and my primary consideration was performance i would use Table Functions. mathguys solution works perfectly but it will be more performant if we use a pipelined function.
First we create types which are necessary for our function.
drop type type_test_table;
drop type type_test_row;
CREATE TYPE type_test_row AS OBJECT (
code varchar2(2000),
descr VARCHAR2(50)
)
/
CREATE TYPE type_test_table IS TABLE OF type_test_row
/
Then we create our function :
create or replace function test_pipe_func return type_test_table pipelined as
cursor c_data_in is
select '12361'||level||'_BBMS_GTECHL'||level||'|12362'||level||'_BBMS_PRIM'||level||'|12363'||level||'_BBMS_SEC'||level||'|12364'||level||'_BBU_SEC'||level as str from dual
connect by level <= 1000000;
v_element varchar2(300);
v_code varchar2(100);
v_descr varchar2(200);
p_deb number;
p_fin number;
begin
for l_data_in in c_data_in loop
p_deb := 0;
p_fin := 1;
while p_fin > 0 loop
p_fin := case when p_deb = 0 then instr(l_data_in.str,'|',1, 1) else instr(l_data_in.str,'|',p_deb-1, 2) end;
p_deb := case when p_deb = 0 then 1 else instr(l_data_in.str,'|',p_deb-1, 1)+1 end;
v_element := case when p_fin = 0 then substr(l_data_in.str, p_deb) else substr(l_data_in.str, p_deb, p_fin - p_deb) end;
p_deb := p_fin +1;
v_code := substr(v_element, 1 , instr(v_element, '_' , 1,1)-1);
v_descr := substr(v_element, instr(v_element, '_' , 1,1)+1);
pipe row(type_test_row(v_code, v_descr));
end loop;
end loop;
end test_pipe_func;
/
I changed the test case a little bit to be able to generate as many lines as necessary for my tests. And i used a pipelined function to limit usage of Process memory in case of big datasets and to be able to use it with a select. If your use case is different(i don't know maybe to insert into a table using the input) another option can be to use bulk collect into and forall.
create or replace procedure test_bulk_collect_proc as
cursor c_data_in is
select '12361'||level||'_BBMS_GTECHL'||level||'|12362'||level||'_BBMS_PRIM'||level||'|12363'||level||'_BBMS_SEC'||level as str from dual
connect by level <= 1000000;
type type_table_data_in is table of c_data_in%rowtype;
table_data_in type_table_data_in;
v_element varchar2(300);
v_code varchar2(100);
v_descr varchar2(200);
p_deb number;
p_fin number;
v_str varchar2(4000);
v_t_insr type_test_table;
limit_in number := 100000;
i number;
begin
OPEN c_data_in;
LOOP
FETCH c_data_in BULK COLLECT INTO table_data_in LIMIT limit_in;
v_t_insr := type_test_table();
i := 1;
for indx IN 1 .. table_data_in.COUNT LOOP
v_str := table_data_in(indx).str;
p_deb := 0;
p_fin := 1;
while p_fin > 0 loop
p_fin := case when p_deb = 0 then instr(v_str,'|',1, 1) else instr(v_str,'|',p_deb-1, 2) end;
p_deb := case when p_deb = 0 then 1 else instr(v_str,'|',p_deb-1, 1)+1 end;
v_element := case when p_fin = 0 then substr(v_str, p_deb) else substr(v_str, p_deb, p_fin - p_deb) end;
p_deb := p_fin +1;
v_code := substr(v_element, 1 , instr(v_element, '_' , 1,1)-1);
v_descr := substr(v_element, instr(v_element, '_' , 1,1)+1);
v_t_insr.extend;
v_t_insr(i) := type_test_row(v_code, v_descr);
i:= i+1;
end loop;
END LOOP;
forall t in v_t_insr.first..v_t_insr.last
insert into test_bbu(CODE, DESCR) values (v_t_insr(t).code, v_t_insr(t).descr);
EXIT WHEN table_data_in.COUNT < limit_in;
END LOOP;
End;
/
I tested all three methods on my database. To test the sql of mathguy and the pipelined function i used CTAS and for the bulk collect into i simply executed the procedure.
create table test_bbu as
with input_data (input_str) as (
select '12361'||level||'_BBMS_GTECHL'||level||'|12362'||level||'_BBMS_PRIM'||level||'|12363'||level||'_BBMS_SEC'||level from dual
connect by level <= 1000000
),
t (str) as (
select '|' || input_str || '|' from input_data
),
r (lvl, code, descr, str, p1_from, p2_from, p1_to, p2_to) as (
select 0, null, null, str, 1, 1, instr(str, '_', 1, 1), instr(str, '|', 1, 2)
from t
union all
select lvl+1, substr(str, p2_from + 1, p1_to - p2_from - 1),
substr(str, p1_to + 1, p2_to - p1_to - 1),
str, p1_to, p2_to, instr(str, '_', p2_to + 1, 1),
instr(str, '|', p2_to + 1, 1)
from r
where p1_to != 0
)
select code, descr
from r
where lvl != 0;
create table test_bbu2 as
select * from table(test_pipe_func);
execute test_bulk_collect_proc;
I tested three methods with 500K and 1M lines. Here are my results but i urge you to test on your environnement before you make your decision.
500K 1M
----------------------------------------
SQL 36s 1m:15s
Pipelined 11s 23s
Bulk Collect 8s 17s

Related

How to get a data between vertical bar using PL/SQL

Table DATA:
id text
1 CLARE|FEMALE|PRESIDENT
2 SCARLET|FEMALE|MANAGER
3 FRANK|MALE|ANALYST
...
What I need to do is to get all data between vertical bar and put it in my variables(for insert to another table),
here what i tried :
DECLARE
v_NAME varchar2(100);
v_GENDER nvarchar2(100);
v_POSITION nvarchar2(100);
CURSOR c1
IS
SELECT *
FROM DATA
ORDER BY id;
BEGIN
FOR x IN c1 LOOP
v_NAME := REGEXP_SUBSTR(x.TEXT_NOTE, '[^|]+', 1, 1);
v_GENDER := REGEXP_SUBSTR(x.TEXT_NOTE, '[^|]+', 1, 2);
v_POSITION := REGEXP_SUBSTR(x.TEXT_NOTE, '[^|]+', 1, 3);
DBMS_OUTPUT.PUT_LINE(v_NAME ,v_GENDER ,v_POSITION); --test
END LOOP;
END;
Obviously I'm not doing this right as it doesn't work. Can somebody suggest a solution?
Thanks!
I think, there are two points to fix in your attempt :
In your LOOP you are referring to column "x.TEXT_NOTE" whereas it should be column "TEXT" from your table DATA.
You need to change that output line DBMS_OUTPUT.PUT_LINE(v_NAME ,v_GENDER ,v_POSITION) by DBMS_OUTPUT.PUT_LINE(v_NAME||', '||v_GENDER||', '||v_POSITION)
CREATE TABLE DATA_TO_SPLIT (id, text) as (
select 1, 'CLARE|FEMALE|PRESIDENT' from dual union all
select 2, 'SCARLET|FEMALE|MANAGER' from dual union all
select 3, 'FRANK|MALE|ANALYST' from dual
)
;
DECLARE
v_NAME varchar2(100);
v_GENDER nvarchar2(100);
v_POSITION nvarchar2(100);
CURSOR c1
IS
SELECT *
FROM DATA_TO_SPLIT
ORDER BY id;
BEGIN
FOR x IN c1 LOOP
v_NAME := REGEXP_SUBSTR(x.TEXT, '[^|]+', 1, 1);
v_GENDER := REGEXP_SUBSTR(x.TEXT, '[^|]+', 1, 2);
v_POSITION := REGEXP_SUBSTR(x.TEXT, '[^|]+', 1, 3);
DBMS_OUTPUT.PUT_LINE(v_NAME||', '||v_GENDER||', '||v_POSITION); --test
END LOOP;
END;
/
The string_split function should work.
SELECT * FROM STRING_SPLIT('CLARE|FEMALE|PRESIDENT', '|')
Indeed no problem seems with the current code except
the typo which needs column name conversion from text_note to
text
and adding an Insert statement for a presumed table (namely
data2) is missing
Btw, current DBMS_OUTPUT.PUT_LINEs syntax is wrong
So, use the following code :
DECLARE
v_name VARCHAR2(100);
v_gender NVARCHAR2(100);
v_position NVARCHAR2(100);
CURSOR c1 IS
SELECT *
FROM data
ORDER BY id;
BEGIN
FOR x IN c1
LOOP
v_name := TRIM(REGEXP_SUBSTR(x.text, '[^|]+', 1, 1));
v_gender := TRIM(REGEXP_SUBSTR(x.text, '[^|]+', 1, 2));
v_position := TRIM(REGEXP_SUBSTR(x.text, '[^|]+', 1, 3));
DBMS_OUTPUT.PUT_LINE(v_name||' - '||v_gender||' - '||v_position);
INSERT INTO data2
VALUES(v_name,v_gender,v_position);
END LOOP;
COMMIT;
END;
/
where TRIM() function stands for removing potential leading and trailing whitespaces
Demo

select in a loop in oracle sql?

My stored procedure is used for getting string values of ids separate by ',' from a string contains names in table AUTH_GROUPS.
for example:
id name
1 role_1
2 role_2
3 role_3
the input is: 'role_1,role_2,role_3'
the output is: '1,2,3'
CREATE OR REPLACE PROCEDURE PROCEDURE1(P_ERROR_MSG OUT VARCHAR2, P_ROLE_STRING IN VARCHAR2 ) AS
BEGIN
DECLARE
lvOutPut VARCHAR2(2000);
vId varchar2(1000);
BEGIN
lvOutPut := '';
FOR i IN
(SELECT trim(regexp_substr(P_ROLE_STRING, '[^,]+', 1, LEVEL)) l
FROM dual
CONNECT BY LEVEL <= regexp_count(P_ROLE_STRING, ',')+1
)
LOOP
select id into vId from AUTH_GROUPS where NAME = i; --this line got error 'expression is of wrong type'
lvOutPut := lvOutPut || vId || ',';
END LOOP;
P_ERROR_MSG := lvOutPut;
P_ERROR_MSG := substr(P_ERROR_MSG, 1, LENGTH(P_ERROR_MSG) - 1);
END;
END PROCEDURE1;
But it has an error in the line that I commented. I tried i.1 or i.value but still got errors.
You need to use the actual column name. i is loop handle name.
....
BEGIN
lvOutPut := '';
FOR i IN
(SELECT trim(regexp_substr(P_ROLE_STRING, '[^,]+', 1, LEVEL)) l -- this is column name to be used inside the loop
FROM dual
CONNECT BY LEVEL <= regexp_count(P_ROLE_STRING, ',')+1
)
LOOP
select id into vId from AUTH_GROUPS where NAME = i.l; -- change here
...
/* Formatted on 6/23/2020 2:08:34 PM (QP5 v5.354) */
CREATE OR REPLACE PROCEDURE PROCEDURE1 (P_ERROR_MSG OUT VARCHAR2,
P_ROLE_STRING IN VARCHAR2)
AS
BEGIN
DECLARE
lvOutPut VARCHAR2 (2000);
vId VARCHAR2 (1000);
BEGIN
lvOutPut := '';
FOR i IN ( SELECT TRIM (REGEXP_SUBSTR (P_ROLE_STRING,
'[^,]+',
1,
LEVEL)) l
FROM DUAL
CONNECT BY LEVEL <= REGEXP_COUNT (P_ROLE_STRING, ',') + 1)
LOOP
SELECT id
INTO vId
FROM AUTH_GROUPS
WHERE NAME = i.l; --this line got error 'expression is of wrong type'
lvOutPut := lvOutPut || vId || ',';
END LOOP;
P_ERROR_MSG := lvOutPut;
P_ERROR_MSG := SUBSTR (P_ERROR_MSG, 1, LENGTH (P_ERROR_MSG) - 1);
END;
END PROCEDURE1;

Comparing two comma delimited strings

I have 2 strings str1: 'abc,def,ghi' and str2: 'tyu,abc,fgh'.
I want to compare these two strings using the delimiter ,. Now since the 2 strings have abc it should return true. I want a function in Oracle SQL which can perform this operation.
Looks complicated but it is just a couple of helper functions to split a list into separate values (to be contained into a table type) and then a very simple function to test the intersection of two collections.
Oracle Setup:
CREATE TYPE VARCHAR2s_Table AS TABLE OF VARCHAR2(4000);
CREATE FUNCTION regexp_escape(
expression VARCHAR2
) RETURN VARCHAR2 DETERMINISTIC
AS
BEGIN
RETURN REGEXP_REPLACE( expression, '([$^[()+*?{\|])', '\\\1', 1, 0, 'c' );
END;
/
CREATE FUNCTION splitList(
list VARCHAR2,
delim VARCHAR2 := ','
) RETURN VARCHAR2s_Table DETERMINISTIC
AS
pattern VARCHAR2(256);
len BINARY_INTEGER;
t_items VARCHAR2s_Table := VARCHAR2s_Table();
BEGIN
IF list IS NULL THEN
NULL;
ELSIF delim IS NULL THEN
t_items.EXTEND( LENGTH( list ) );
FOR i IN 1 .. LENGTH( list ) LOOP
t_items(i) := SUBSTR( list, i, 1 );
END LOOP;
ELSE
pattern := '(.*?)($|' || REGEXP_ESCAPE( delim ) || ')';
len := REGEXP_COUNT( list, pattern ) - 1;
t_items.EXTEND( len );
IF len = 1 THEN
t_items(1) := list;
ELSE
FOR i IN 1 .. len LOOP
t_items(i) := REGEXP_SUBSTR( list, pattern, 1, i, NULL, 1 );
END LOOP;
END IF;
END IF;
RETURN t_items;
END;
/
CREATE FUNCTION check_list_intersect(
list1 VARCHAR2,
list2 VARCHAR2
) RETURN NUMBER DETERMINISTIC
AS
BEGIN
IF splitList( list1 ) MULTISET INTERSECT splitList( list2 ) IS EMPTY THEN
RETURN 0;
ELSE
RETURN 1;
END IF;
END;
/
Query 1:
SELECT check_list_intersect( 'abc,def,ghi', 'abc' ) AS matches
FROM DUAL;
Results:
MATCHES
---------
1
Query 2:
SELECT check_list_intersect( 'abc,def,ghi', 'abcd' ) AS matches
FROM DUAL;
Results:
MATCHES
---------
0
The below will make the trick.
with temp as (
select 1 strid, 'abc,def,ghi' Error from dual
union all
select 2, 'tyu,abc,fgh' from dual
)
select str
from (
SELECT strid, trim(regexp_substr(str, '[^,]+', 1, level)) str
FROM (SELECT strid, Error str FROM temp) t
CONNECT BY instr(str, ',', 1, level - 1) > 0
)
group by str
having count(distinct strid) > 1;
Warning: This answer is not fully correct. (See the comments.)
Starting from dcieslak answer(csv split with regexp), a variation of the subject would be:
create or replace function check_string_intersec(str1 varchar2, str2 varchar2) return number
is
begin
for k in (SELECT trim(regexp_substr(str1, '[^,]+', 1, level)) item
FROM dual
CONNECT BY instr(str1, ',', 1, level - 1) > 0
)
loop
if instr(str2, k.item,1) > 0 then return 1; end if;
end loop;
return 0;
end;
This splits first string and search for every item in the second string.

oracle sql split string to rows

I have string 'ABC' I need to split into rows as below
A
B
C
.I know how do do when delimiter is present. How about when delimiter is not present
with test as
(select 'A,B,C' col1 from dual)
select regexp_substr(col1, '[^,]+', 1, rownum) result1
from test
connect by level <= length(regexp_replace(col1, '[^,]+')) + 1;
Without a delimiter it should be even easier - use the same approach, but just use substr with level as the index of the string:
with test as
(select 'ABC' col1 from dual)
select substr(col1, level, 1) result1
from test
connect by level <= length(col1);
You can use a function like this
-- define type
CREATE OR REPLACE TYPE TABLE_OF_STRING AS TABLE OF VARCHAR2(32767);
-- function
function SPLIT_STRING_TO_STRINGS
(
p_list varchar2,
p_delimiter varchar2 := ','
) return TABLE_OF_STRING pipelined
is
l_idx pls_integer;
l_list varchar2(32767) := p_list;
begin
loop
l_idx := instr(l_list, p_delimiter);
if l_idx > 0 then
pipe row(substr(l_list, 1, l_idx-1));
l_list := substr(l_list, l_idx + length(p_delimiter));
else
pipe row(l_list);
exit;
end if;
end loop;
return;
end SPLIT_STRING_TO_STRINGS;
-- usage example
select * from table(SPLIT_STRING_TO_STRINGS('A,B,C',','))

Function to convert IP address in Integer using sql which will run on any database

I wrote function
CREATE FUNCTION ip2int(text) RETURNS bigint AS $$
SELECT split_part($1,'.',1)::bigint*16777216 + split_part($1,'.',2)::bigint*65536 +
split_part($1,'.',3)::bigint*256 + split_part($1,'.',4)::bigint;
$$ LANGUAGE SQL IMMUTABLE RETURNS NULL ON NULL INPUT;
SELECT ip2int('200.233.1.2');
which works on postgres database.
But when I run it on Oracle It gave me following error
Error(1,21): PLS-00103: Encountered the symbol ")" when expecting one
of the following: in out long double ref char time timestamp interval
date binarynational character nchar
What is the solution for this?
Is there any way to make it database independent?
I realize that in PostgreSQL it's common to refer to one of the more commonly used programming languages as "plsql", but the language supported by PostgreSQL is properly known as "plpgsql", and PL/SQL and PL/pgSQL are two similar-but-different languages. The syntax of this function is not valid PL/SQL as defined by Oracle. PL/SQL does not use $$ to quote the text of the routine; the text parameter is untyped, which is not allowed in PL/SQL; PL/SQL does not support the $1, $2, etc parameter markers; bigint isn't supported in Oracle (although you could define a subtype of NUMBER to handle this); Oracle doesn't support the :: cast operator; doesn't provide split_part (although I suppose you could write your own); and doesn't use the LANGUAGE SQL IMMUTABLE... stuff at the end. Oracle requires that a function consist of a statement block (DECLARE...BEGIN...END), while this function is purely a SELECT statement; and the SELECT statement in the function isn't valid for Oracle because Oracle requires a FROM clause. So, basically, this function is not valid PL/SQL. You could transform it into something like
CREATE OR REPLACE FUNCTION ip2int(text IN VARCHAR2)
RETURN NUMBER
AS
nRetval NUMBER;
BEGIN
SELECT TO_NUMBER(REGEXP_SUBSTR(text, '[0-9]', 1, 1)) * 16777216 +
TO_NUMBER(REGEXP_SUBSTR(text, '[0-9]', 1, 2)) * 65536 +
TO_NUMBER(REGEXP_SUBSTR(text, '[0-9]', 1, 3)) * 256 +
TO_NUMBER(REGEXP_SUBSTR(text, '[0-9]', 1, 4))
INTO nRetval
FROM DUAL;
RETURN nRetval;
END IP2INT;
but making such radical alterations doesn't seem to be what you want to do.
The PostgreSQL documentation on converting from PL/SQL to PL/pgSQL might give you some ideas of the challenges involved in this.
Best of luck.
CREATE OR REPLACE FUNCTION split_part(string VARCHAR2, delimiter VARCHAR2, n NUMBER)
RETURN VARCHAR2
IS
v_start NUMBER(5) := 1;
v_end NUMBER(5);
BEGIN
-- Find the position of n-th -1 delimiter
IF n > 1 THEN
v_start := INSTR(string, delimiter, 1, n - 1);
-- Delimiter not found
IF v_start = 0 THEN
RETURN NULL;
END IF;
v_start := v_start + LENGTH(delimiter);
END IF;
-- Find the position of n-th delimiter
v_end := INSTR(string, delimiter, v_start, 1);
-- If not found return until the end of string
IF v_end = 0 THEN
RETURN SUBSTR(string, v_start);
END IF;
RETURN SUBSTR(string, v_start, v_end - v_start);
END;
CREATE OR REPLACE FUNCTION ip2int(text IN VARCHAR2)
RETURN NUMBER
AS
nRetval NUMBER;
BEGIN
SELECT TO_NUMBER(split_part(text,'.',1)) * 16777216 +
TO_NUMBER(split_part(text,'.',2)) * 65536 +
TO_NUMBER(split_part(text,'.',3)) * 256 +
TO_NUMBER(split_part(text,'.',4))
INTO nRetval
FROM DUAL;
RETURN nRetval;
END IP2INT;
create or replace FUNCTION ip_to_number(ip IN VARCHAR2)
RETURN NUMBER
AS
a varchar(20 char);
b varchar(20 char);
c varchar(20 char);
d varchar(20 char);
i integer;
j integer;
k integer;
ip_value integer;
begin
i := instr(ip, '.');
j := instr(ip, '.', 1, 2);
k := instr(ip, '.', 2, 3);
if (k = 0) then
raise_application_error(-20101, 'Incorrect IP format (missing at least one dot)');
end if;
a := substr(ip, 1, i - 1);
b := substr(ip, i + 1, j - i - 1);
c := substr(ip, j + 1, k - j - 1);
d := substr(ip, k + 1);
if (a not between 0 and 255) or (b not between 0 and 255) or (c not between 0 and 255) or (d not between 0 and 255) then
raise_application_error(-20102, 'Incorrect IP format (octet out of bounds)');
end if;
ip_value := to_number(a) * 16777216 + to_number(b) * 65536 + to_number(c) * 256 + to_number(d);
RETURN ip_value;
end;
You can convert it onflight:
select TO_NUMBER(REGEXP_SUBSTR(YourIpAddress, '[0-9]+', 1, 1)) * power(2, 24) +
TO_NUMBER(REGEXP_SUBSTR(YourIpAddress, '[0-9]+', 1, 2)) * power(2, 16) +
TO_NUMBER(REGEXP_SUBSTR(YourIpAddress, '[0-9]+', 1, 3)) * power(2, 8) +
TO_NUMBER(REGEXP_SUBSTR(YourIpAddress, '[0-9]+', 1, 4))
from dual;
in this block you can easily check this translation
declare
YourIpAddress varchar2(32) := '192.168.1.1';
res number;
begin
select TO_NUMBER(REGEXP_SUBSTR(YourIpAddress, '[0-9]+', 1, 1)) * power(2, 24) +
TO_NUMBER(REGEXP_SUBSTR(YourIpAddress, '[0-9]+', 1, 2)) * power(2, 16) +
TO_NUMBER(REGEXP_SUBSTR(YourIpAddress, '[0-9]+', 1, 3)) * power(2, 8) +
TO_NUMBER(REGEXP_SUBSTR(YourIpAddress, '[0-9]+', 1, 4))
into res
from dual;
DBMS_OUTPUT.put_line(res);
end;
on this site you can check correctness.