I have a table (WORKLOG) in an Oracle SQL database which has a VARCHAR description field of 250 characters. On a separate table (LONGDESCRIPTION) there is a CLOB field which holds a long description. My goal is to build a query which returns the first 4000 characters of the concatenation of the short and long descriptions.
I initially tried this with SUBSTR functions, but that didn't work so well with the CLOB: it was overflowing the VARCHAR(4000) field I'd set up for it (ORA-64203: Destination buffer too small to hold CLOB data after character set conversion.). After searching on here I tried using DBMS_LOB.SUBSTR, but this didn't seem to work either: I got the same 64203, or ORA-22831: Offset or offset+amount does not land on character boundary. I have tried a few ways to fix it, using slightly different functions, but each runs into trouble, and I'm having no luck working out a way to avoid cutting a character but also ensuring I don't overflow the 4000 limit.
Here's the code I'm using. The idea is that it should use the short description if there is no long description, use a truncated long description if there is no short description or if the short description is the same as the start of the long description, and otherwise concatenate the two. The REGEXP functions are to try and remove HTML formatting.
SELECT
WORKLOG.WORKLOGID as Worklog,
WORKLOG.DESCRIPTION as Description,
CASE
WHEN WORKLOG.DESCRIPTION is null AND LENGTH(REGEXP_REPLACE(LONGDESCRIPTION.LDTEXT, '<.*?>')) > 4000
THEN CAST(SUBSTR(REGEXP_REPLACE(LONGDESCRIPTION.LDTEXT, '<.*?>'), 1, 3996) as VARCHAR(3996)) || '...'
WHEN WORKLOG.DESCRIPTION is null
THEN CAST(REGEXP_REPLACE(LONGDESCRIPTION.LDTEXT, '<.*?>') as VARCHAR(4000))
WHEN LONGDESCRIPTION.LDKEY is null
THEN WORKLOG.DESCRIPTION
WHEN CAST(SUBSTR(REGEXP_REPLACE(LONGDESCRIPTION.LDTEXT, '<.*?>'), 1, LENGTH(WORKLOG.DESCRIPTION)) as VARCHAR(4000)) = WORKLOG.DESCRIPTION AND LENGTH(REGEXP_REPLACE(LONGDESCRIPTION.LDTEXT, '<.*?>')) > 4000
THEN CAST(SUBSTR(REGEXP_REPLACE(LONGDESCRIPTION.LDTEXT, '<.*?>'), 1, 3996) as VARCHAR(3996)) || '...'
WHEN CAST(SUBSTR(REGEXP_REPLACE(LONGDESCRIPTION.LDTEXT, '<.*?>'), 1, LENGTH(WORKLOG.DESCRIPTION)) as VARCHAR(4000)) = WORKLOG.DESCRIPTION
THEN CAST(SUBSTR(REGEXP_REPLACE(LONGDESCRIPTION.LDTEXT, '<.*?>'), 1, 4000) as VARCHAR(4000))
WHEN LENGTH(REGEXP_REPLACE(LONGDESCRIPTION.LDTEXT, '<.*?>')) > 3732
THEN WORKLOG.DESCRIPTION || ' // ' || CAST(SUBSTR(REGEXP_REPLACE(LONGDESCRIPTION.LDTEXT, '<.*?>'), 1, 3732) as VARCHAR(3732)) || '...'
ELSE WORKLOG.DESCRIPTION || ' // ' || CAST(SUBSTR(REGEXP_REPLACE(LONGDESCRIPTION.LDTEXT, '<.*?>'), 1, 3732) as VARCHAR(3735))
END as Description_Full
FROM
Maximo.WORKLOG
LEFT JOIN
Maximo.LONGDESCRIPTION
ON LONGDESCRIPTION.LDOWNERTABLE = 'WORKLOG'
AND LONGDESCRIPTION.LDOWNERCOL = 'DESCRIPTION'
AND LONGDESCRIPTION.LDKEY = WORKLOG.WORKLOGID
AND LENGTH(REGEXP_REPLACE(LONGDESCRIPTION.LDTEXT, '<.*?>')) > 0
Examples where the description field is 2 characters long and the maximum is 10:
Description
Long description
Expected result
ABC
ABC
ABCDEFGHIJKL
ABCDEFGHIJ
AB
AB
AB
ABCDE
ABCDE
AB
ABCDEFGHIJKL
ABCDEFGHIJ
AB
XY
AB // XY
AB
XYZABCDEF
AB // XYZA
Any help would be greatly appreciated.
Assuming that your CLOB contains well-formed XML or XHTML then you should use a proper XML parser (and not regular expressions) and can use:
SELECT w.WORKLOGID as Worklog,
w.DESCRIPTION as Description,
SUBSTR(
CASE
WHEN w.description IS NOT NULL AND xml.text LIKE w.description || '%'
THEN xml.text
WHEN w.description IS NOT NULL AND xml.text IS NOT NULL
THEN TO_CLOB(w.DESCRIPTION) || '//' || xml.text
WHEN w.description IS NOT NULL
THEN TO_CLOB(w.DESCRIPTION)
WHEN xml.text IS NOT NULL
THEN xml.text
END,
1,
4000
) AS Description_Full
FROM /*Maximo.*/WORKLOG w
LEFT JOIN (
/*Maximo.*/LONGDESCRIPTION ld
CROSS APPLY XMLTABLE(
'/HTML/BODY'
PASSING XMLTYPE(
COALESCE(ld.LONGDESCRIPTION, TO_CLOB('<HTML></HTML>') )
)
COLUMNS
text CLOB PATH '.'
) xml
)
ON ld.LDOWNERTABLE = 'WORKLOG'
AND ld.LDOWNERCOL = 'DESCRIPTION'
AND ld.LDKEY = w.WORKLOGID
Which, for the sample data:
CREATE TABLE worklog (
worklogid NUMBER,
description VARCHAR2(250)
);
CREATE TABLE longdescription (
LDOWNERTABLE VARCHAR2(30),
LDOWNERCOL VARCHAR2(30),
LDKEY NUMBER,
LONGDESCRIPTION CLOB
);
INSERT INTO worklog (worklogid, description)
SELECT 1, 'Value1' FROM DUAL UNION ALL
SELECT 2, 'Value2' FROM DUAL UNION ALL
SELECT 3, NULL FROM DUAL UNION ALL
SELECT 4, NULL FROM DUAL;
INSERT INTO longdescription
SELECT 'WORKLOG', 'DESCRIPTION', 1, '<HTML><BODY>Test</BODY></HTML>' FROM DUAL UNION ALL
SELECT 'WORKLOG', 'DESCRIPTION', 2, NULL FROM DUAL UNION ALL
SELECT 'WORKLOG', 'DESCRIPTION', 3, '<HTML><BODY>Test</BODY></HTML>' FROM DUAL UNION ALL
SELECT 'WORKLOG', 'DESCRIPTION', 4, NULL FROM DUAL;
Outputs:
WORKLOG
DESCRIPTION
DESCRIPTION_FULL
1
Value1
Value1//Test
3
null
Test
2
Value2
Value2
4
null
null
db<>fiddle here
Related
I have below query of which i would like to modify.
I want to sum all the numbers that occur within a string with the condition that it is joined with the text GB or MB .
If it is in GB it first has to be converted to MB. (This i have done simply by multiplying by 1024)
SELECT /*+ PARALLEL */
'SOME TEXT 20GB+2GB+SOMETEXT' SOMETEXT,
CASE
WHEN REGEXP_SUBSTR('SOME TEXT 20GB+2GB+SOMETEXT','GB',1,1) = 'GB'
THEN 1024*to_number(regexp_replace(REGEXP_SUBSTR('SOME TEXT 20GB+2GB+SOMETEXT','(\d+)GB',1,1), '[^0-9]', ''))
ELSE to_number(regexp_replace(REGEXP_SUBSTR('SOME TEXT 20GB+2GB+SOMETEXT','(\d+)MB',1,1), '[^0-9]', ''))
END TOTAL_MBs
FROM DUAL;
TEST STRINGS
TEXT TEXT_35MB+ MORETEXT
OTHERTEXT 480MB + 3MB AND_TEXT
SOMETEXT 7MB + 7NUMBER
TEXT 1GB AND SOME_TEXT
SOME TEXT 20GB+2GB+SOMETEXT
Here is where i am stuck: To add the numbers that occur more than once in one text
For example:-
For this text OTHERTEXT 480MB + 3MB AND_TEXT I want my result to have 483 as TOTAL_MBS and not 480
Think you are searching for something like:
with da as (
select 1 id, 'TEXT TEXT_35MB+ MORETEXT' tcase from dual
union all select 2 id,'OTHERTEXT 480MB + 3MB AND_TEXT' tcase from dual
union all select 3 id,'SOMETEXT 7MB + 7NUMBER' tcase from dual
union all select 4 id,'TEXT 1GB AND SOME_TEXT' tcase from dual
union all select 5 id,'SOME TEXT 20GB+2GB+SOMETEXT' tcase from dual
union all select 6 id,'SOME TEXT 20MB+2GB+SOMETEXT' tcase from dual
),
split as(
select id
, tcase
, REGEXP_SUBSTR(tcase,'(\d+)(M|G)B',1,level) ot
from da
connect by REGEXP_SUBSTR(tcase,'(\d+)(M|G)B',1,level)is not null
and prior id = id
and PRIOR DBMS_RANDOM.VALUE IS NOT NULL)
select id
, tcase
, sum( case when ot like '%GB%' then 1024 else 1 end * regexp_substr(ot,'\d+')) v
from split
group by id
,tcase
order by id;
Result:
1 TEXT TEXT_35MB+ MORETEXT 35
2 OTHERTEXT 480MB + 3MB AND_TEXT 483
3 SOMETEXT 7MB + 7NUMBER 7
4 TEXT 1GB AND SOME_TEXT 1024
5 SOME TEXT 20GB+2GB+SOMETEXT 22528
6 SOME TEXT 20MB+2GB+SOMETEXT 2068
You can use a recursive sub-query factoring clause:
SELECT sometext,
COALESCE(
REGEXP_SUBSTR( sometext, '(\d+)([MG])B', 1, 1, NULL, 1 )
* CASE REGEXP_SUBSTR( sometext, '(\d+)([MG])B', 1, 1, NULL, 2 )
WHEN 'M' THEN 1
WHEN 'G' THEN 1024
END,
0
),
1,
REGEXP_COUNT( sometext, '(\d+)([MG])B' )
FROM test_data
UNION ALL
SELECT sometext,
total_mb
+ REGEXP_SUBSTR( sometext, '(\d+)([MG])B', 1, i + 1, NULL, 1 )
* CASE REGEXP_SUBSTR( sometext, '(\d+)([MG])B', 1, i + 1, NULL, 2 )
WHEN 'M' THEN 1
WHEN 'G' THEN 1024
END,
i + 1,
num_terms
FROM terms
WHERE i < num_terms
)
SELECT sometext,
total_mb
FROM terms
WHERE i >= num_terms;
which for the test data:
CREATE TABLE test_data ( sometext ) AS
SELECT 'SOME TEXT 20GB+2GB+SOMETEXT' FROM DUAL UNION ALL
SELECT '1MB+1GB+10MB+10GB' FROM DUAL;
outputs:
SOMETEXT | TOTAL_MB
:-------------------------- | -------:
SOME TEXT 20GB+2GB+SOMETEXT | 22528
1MB+1GB+10MB+10GB | 11275
db<>fiddle here
below I used a view/memory table to assign regex function to the specific string and it worked for me
with tbl1 as (
select 1 pd, ' 20GB+2GB sometext +7500 + 45sometext' string from dual
),
tbl2 as(
select pd
, string
, REGEXP_SUBSTR(string,'(\d+)(M|G)B',1,level) string2
from tbl1
connect by REGEXP_SUBSTR(string,'(\d+)(M|G)B',1,level)is not NULL
and prior pd = pd
and PRIOR DBMS_RANDOM.VALUE IS NOT NULL)
select pd
, string
, sum( case when string2 like '%GB%' then 1024 end * regexp_substr(string2,'\d+')) string3
from tbl2
group by pd
,string
order by pd;
I was trying to fetch data with condition "get data only if string contains the lower char in the 4th and 5th position".
Below are the conditions:
and ascii(substr(RAP01.CRDVER,4,1)) between 97 and 122
and ascii(substr(RAP01.CRDVER,5,1)) between 97 and 122;
Table name RAP01 and column name CRDVER.
But It is not fetching all the required data.
Can this approach is correct?
How about this? The key is line 8 - query will return strings whose 4th and 5th character (substr(col, 4, 2))is a lowercase letter ([a-z]) (c means that the search is case sensitive).
SQL> with test (col) as
2 (select '12Bcfx23' from dual union all
3 select '123456' from dual union all
4 select 'ABCDEFGH' from dual
5 )
6 select col, substr(col, 4, 2) sub
7 from test
8 where regexp_like(substr(col, 4, 2), '[a-z]', 'c');
COL SU
-------- --
12Bcfx23 cf
SQL>
Are lower case a subset of ASCII? NO
Can I use ASCII to identify lower case? NO
How can I know if a string has lower case? It has lower case if UPPER of string is not the same as just the string.
Sample:
create table t1 ( a varchar(100) )\\
insert into t1 (a) values ( 'A' )\\
insert into t1 (a) values ( 'a' )\\
insert into t1 (a) values ( 'å' )\\
insert into t1 (a) values ( 'æ' )\\
select (case when a = upper( a ) <-- compare with upper
then a || ' Is Upper'
else a || ' Has lower' end) as r_upper,
(case when not regexp_like(a, '[a-z]', 'c') <-- using ascii
then a || ' Is Upper'
else a || ' Has lower' end) as r_reg
from t1\\
R_UPPER | R_REG
-------------------------
A Is Upper | A Is Upper
a Has lower | a Has lower
å Has lower | å Is Upper (¹)
æ Has lower | æ Is Upper (¹)
(¹) Wrong result using ASCII.
I have a description column in my table and its values are:
This is a EXAMPLE
This is a TEST
This is a VALUE
I want to display only EXAMPLE, TEST, and VALUE from the description column.
How do I achieve this?
This could be a way:
-- a test case
with test(id, str) as (
select 1, 'This is a EXAMPLE' from dual union all
select 2, 'This is a TEST' from dual union all
select 3, 'This is a VALUE' from dual union all
select 4, 'This IS aN EXAMPLE' from dual
)
-- concatenate the resulting words
select id, listagg(str, ' ') within group (order by pos)
from (
-- tokenize the strings by using the space as a word separator
SELECT id,
trim(regexp_substr(str, '[^ ]+', 1, level)) str,
level as pos
FROM test t
CONNECT BY instr(str, ' ', 1, level - 1) > 0
and prior id = id
and prior sys_guid() is not null
)
-- only get the uppercase words
where regexp_like(str, '^[A-Z]+$')
group by id
The idea is to tokenize every string, then cut off the words that are not made by upper case characters and then concatenate the remaining words.
The result:
1 EXAMPLE
2 TEST
3 VALUE
4 IS EXAMPLE
If you need to handle some other character as an upper case letter, you may edit the where condition to filter for the matching words; for example, with '_':
with test(id, str) as (
select 1, 'This is a EXAMPLE' from dual union all
select 2, 'This is a TEST' from dual union all
select 3, 'This is a VALUE' from dual union all
select 4, 'This IS aN EXAMPLE' from dual union all
select 5, 'This IS AN_EXAMPLE' from dual
)
select id, listagg(str, ' ') within group (order by pos)
from (
SELECT id,
trim(regexp_substr(str, '[^ ]+', 1, level)) str,
level as pos
FROM test t
CONNECT BY instr(str, ' ', 1, level - 1) > 0
and prior id = id
and prior sys_guid() is not null
)
where regexp_like(str, '^[A-Z_]+$')
group by id
gives:
1 EXAMPLE
2 TEST
3 VALUE
4 IS EXAMPLE
5 IS AN_EXAMPLE
Here's another solution. It was inspired by Aleksej's answer.
The idea? Get all the words. Then aggregate only fully uppercased to a list.
Sample data:
create table descriptions (ID int, Description varchar2(100));
insert into descriptions (ID, Description)
select 1 as ID, 'foo Foo FOO bar Bar BAR' as Description from dual
union all select 2, 'This is an EXAMPLE TEST Description VALUE' from dual
;
Query:
select id, Description, listagg(word, ',') within group (order by pos) as UpperCaseWords
from (
select
id, Description,
trim(regexp_substr(Description, '\w+', 1, level)) as word,
level as pos
from descriptions t
connect by regexp_instr(Description, '\s+', 1, level - 1) > 0
and prior id = id
and prior sys_guid() is not null
)
where word = upper(word)
group by id, Description
Result:
ID | DESCRIPTION | UPPERCASEWORDS
-- | ----------------------------------------- | ------------------
1 | foo Foo FOO bar Bar BAR | FOO,BAR
2 | This is an EXAMPLE TEST Description VALUE | EXAMPLE,TEST,VALUE
It is possible to achieve this thanks to the REGEXP_REPLACE function:
SELECT REGEXP_REPLACE(my_column, '(^[A-Z]| |[a-z][A-Z]*|[A-Z]*[a-z])', '') AS Result FROM my_table
It uses a regex which replaces first upper case char of the line and converts every lower case char and space with blanks.
Try this:
SELECT SUBSTR(column_name, INSTR(column_name,' ',-1) + 1)
FROM your_table;
This should do the trick:
SELECT SUBSTR(REGEXP_REPLACE(' ' || REGEXP_REPLACE(description, '(^[A-Z]|[a-z]|[A-Z][a-z]+|[,])', ''), ' +', ' '), 2, 9999) AS only_upper
FROM (
select 'Hey IF you do not know IT, This IS a test of UPPERCASE and IT, with good WILL and faith, Should BE fine to be SHOWN' description
from dual
)
I have added condition to strip commas, you can add inside that brakets other special characters to remove.
ONLY_UPPER
-----------------------------------
IF IT IS UPPERCASE IT WILL BE SHOWN
This is a function based on some of the regular expression answers.
create or replace function capwords(orig_string varchar2)
return varchar2
as
out_string varchar2(80);
begin
out_string := REGEXP_REPLACE(orig_string, '([a-z][A-Z_]*|[A-Z_]*[a-z])', '');
out_string := REGEXP_REPLACE(trim(out_string), '( *)', ' ');
return out_string;
end;
/
Removes strings of upper case letters and underscores that have lower case letters
on either end. Replaces multiple adjacent spaces with one space.
Trims extra spaces off of the ends. Assumes max size of 80 characters.
Slightly edited output:
>select id,str,capwords(str) from test;
ID STR CAPWORDS(STR)
---------- ------------------------------ ------------------
1 This is a EXAMPLE EXAMPLE
2 This is a TEST TEST
3 This is a VALUE VALUE
4 This IS aN EXAMPLE IS EXAMPLE
5 This is WITH_UNDERSCORE WITH_UNDERSCORE
6 ThiS IS aN EXAMPLE IS EXAMPLE
7 thiS IS aN EXAMPLE IS EXAMPLE
8 This IS wiTH_UNDERSCORE IS
If you only need to "display" the result without changing the values in the column then you can use CASE WHEN (in the example Description is the column name):
Select CASE WHEN Description like '%EXAMPLE%' then 'EXAMPLE' WHEN Description like '%TEST%' then 'TEST' WHEN Description like '%VALUE%' then 'VALUE' END From [yourTable]
The conditions are not case sensitive even if you write it all in uppercase.
You can add Else '<Value if all conditions are wrong>' before the END in case there are descriptions that don't contain any of the values. The example will return NULL for those cases, and writing ELSE Description will return the original value of that row.
It also works if you need to update. It is simple and practical, easy way out, haha.
I have to write a query on a table which has a varchar column. Value in this column may have a numbers as substring
Lets possible say the column values are
Data
-----------------------
abc=123/efg=143/ijk=163
abc=123/efg=153/ijk=173
now I have to query the table where data contains the numbers [123,143,163] but shouldnt contain any other number.
How can I write this select query ?
This looks like a very bad database design. If you are interested in separate information stored in a string, then don't store the string but the separate information in separate columns. Change this if possible and such queries will become super simple.
However, for the time being it's easy to find the records as described, provided there are always three numbers in the string as in your sample data. Add a slash at the end of the string, so every number has a leading = and a trailing /. Then look up the numbers in the string with LIKE.
select *
from mytable
where data || `/` like '%=123/%'
and data || `/` like '%=143/%'
and data || `/` like '%=163/%';
If these three numbers are in the string, then all numbers match. Hence there is no other number not matching.
If there can be more numbers in the string but no duplicates, then count equal signs to determine how many numbers are in the string:
select *
from mytable
where data || '/' like '%=123/%'
and data || '/' like '%=143/%'
and data || '/' like '%=163/%'
and regexp_count(data, '=') = 3;
And here is a query accepting even duplicate numbers in the string:
select *
from mytable
where regexp_count(data, '=') >= 3
and regexp_count(data, '=') =
regexp_count(data || '/', '=123/') +
regexp_count(data || '/', '=143/') +
regexp_count(data || '/', '=163/');
Oracle Setup:
CREATE TABLE table_name ( data ) AS
SELECT 'abc=123/efg=143/ijk=163' FROM DUAL UNION ALL
SELECT 'abc=123/efg=153/ijk=173' FORM DUAL;
Then you can create some virtual columns to represent the data:
ALTER TABLE table_name ADD abc GENERATED ALWAYS AS (
TO_NUMBER( REGEXP_SUBSTR( data, '(^|/)abc=(\d+)(/|$)', 1, 1, NULL, 2 ) )
) VIRTUAL;
ALTER TABLE table_name ADD efg GENERATED ALWAYS AS (
TO_NUMBER( REGEXP_SUBSTR( data, '(^|/)efg=(\d+)(/|$)', 1, 1, NULL, 2 ) )
) VIRTUAL;
ALTER TABLE table_name ADD ijk GENERATED ALWAYS AS (
TO_NUMBER( REGEXP_SUBSTR( data, '(^|/)ijk=(\d+)(/|$)', 1, 1, NULL, 2 ) )
) VIRTUAL;
And can add appropriate indexes if you want:
CREATE INDEX table_name__abc_efg_ijk__idx ON table_name( abc, efg, ijk );
Query:
Then if you are only going to have those three keys you can do:
SELECT abc, efg, ijk
FROM table_name
WHERE abc = 123
AND efg = 143
AND ijk = 163;
However, if you could get more than three keys and want ignore additional values then you could do:
CREATE TYPE intlist AS TABLE OF INT;
/
SELECT *
FROM table_name
WHERE INTLIST( 143, 123, 163 )
=
CAST(
MULTISET(
SELECT TO_NUMBER(
REGEXP_SUBSTR(
t.data,
'[^/=]+=(\d+)(/|$)',
1,
LEVEL,
NULL,
1
)
)
FROM DUAL
CONNECT BY LEVEL <= REGEXP_COUNT( t.data, '[^/=]+=(\d+)(/|$)' )
)
AS INTLIST
);
This has the added bonus that INTLIST(123, 143, 163) can be passed as a bind parameter (depending on the client program you are using and the Oracle driver) so that you can simply change how many and what numbers you want to filter for (and that the order of the values does not matter).
Also, if you want it to contain at least those values then you can change INTLIST( ... ) = to INTLIST( ... ) SUBMULTISET OF.
I have a string like this '102/103/104/106'
Now if i pass 102 as input then output should be the next field that is 103. if 103 then output should be 104 and if 106 then output should be null(as for last field I don't have any further expression). I can do this using procedure by splitting the string into arrays and comparing. But can I do this through sql statement something like this
select '102/103/104/106' from dual where [expression 102 or 103].
Thanks!!
You can do it in pure SQL with something like this:
--convert your string into rows
with vals as (
select
substr('102/103/104/106',
instr('102/103/104/106', '/', 1, level)-3,
3
) col,
level lvl
from dual
connect by level <= length('102/103/104/106')-length(replace('102/103/104/106', '/'))+1
)
select *
from (
select col,
lead(col) over (order by lvl) next_val -- find the next value in the list
from vals
)
where col = :val;
Basically, convert your string into rows by parsing it. Then use the analytic lead to find the "next" value.
-- p_whole_string = '102/103/104/106'
-- p_prev = '102'
select
regexp_substr(p_whole_string, '(^|/)' || p_prev || '/([^/]+)', 1, 1, null, 2)
as next
from dual;
Added NVL to return last value if 106 is entered:
SELECT NVL(REGEXP_SUBSTR('102/103/104/106', '(^|/)' || '106' || '/([^/]+)', 1, 1, null, 2), REGEXP_SUBSTR('102/103/104/106', '[^/]+$')) as nxt
FROM dual
/
works for Oracle form 10 up.
SELECT
REGEXP_SUBSTR(
REGEXP_SUBSTR('102/103/104/106', '(^|/)102/[^/]+'), -- returns 102/103
'[^/]+',1,2) val -- takes second part
FROM DUAL;
with parameters looks like this:
-- p_string_to_search = '102/103/104/106'
-- p_string_to_match = '102'
SELECT
REGEXP_SUBSTR(
REGEXP_SUBSTR(p_string_to_search, '(^|/)' || p_string_to_match ||'/[^/]+'), -- returns 102/103
'[^/]+',1,2) val -- takes second part
FROM DUAL;