List functions to find differences between to delimited sets - sql

I have two delimited lists as follows and need to find what has changed.
Example: ( here | is separator )
old string: Joe | Public | NY
new string: Joe | Smith | NY
Since only second member of list has changed, the output should show only what has changed as follows :
Output:
- | Smith | -
Are there any Oracle functions or standard packages available which can compare these two delimited strings/sets and determine what has changed ?

Storing delimited lists in strings is not a good idea; that's missing the point of relational databases (as #Tim pointed out in a comment).
If you are generating the delimited string in an earlier step then go back to the source data and work directly from that.
If you are really stuck with those strings, there are various methods to tokenise delimited strings into individual values (as rows in a result set). This uses regular expression to split both string at once into two columns - for this I'm using a bind variable for each string to void repeating the literals, but you might be getting your strings from table columns (which would need a bit more work) or PL/SQL variables or whatever:
var old_string varchar2(20);
var new_string varchar2(20);
exec :old_string := 'Joe|Public|NY';
exec :new_string := 'Joe|Smith|NY';
with vals (pos, old_val, new_val) as (
select level,
regexp_substr(:old_string, '(.*?)(\||$)', 1, level, null, 1),
regexp_substr(:new_string, '(.*?)(\||$)', 1, level, null, 1)
from dual
connect by level < greatest(regexp_count(:old_string, '(.*?)(\||$)'),
regexp_count(:new_string, '(.*?)(\||$)'))
)
select * from vals;
POS OLD_VAL NEW_VAL
---------- ---------- ----------
1 Joe Joe
2 Public Smith
3 NY NY
It's now simple to compare the two columns to see what has changed, and then (if you must) aggregate them back into a single string value:
with vals (pos, old_val, new_val) as (
select level,
regexp_substr(:old_string, '(.*?)(\||$)', 1, level, null, 1),
regexp_substr(:new_string, '(.*?)(\||$)', 1, level, null, 1)
from dual
connect by level < greatest(regexp_count(:old_string, '(.*?)(\||$)'),
regexp_count(:new_string, '(.*?)(\||$)'))
)
select listagg(case when (old_val is null and new_val is null)
or old_val = new_val then '-' else new_val end, '|')
within group (order by pos) as diff
from vals;
DIFF
--------------------
-|Smith|-
The case expression determines whether you see a dash, indicating no change, or the new value.
This should handle nulls (empty elements, i.e. two delimiters next to each other); it will also handle different numbers of elements, if that can happen:
exec :old_string := 'Joe|Public|NY||';
exec :new_string := 'Joe|Smith|NY||USA';
... same query ...
DIFF
--------------------
-|Smith|-|-|USA
But you should really fix your data model...
If the old and new strings are currently coming from two columns in a table, you could extend this to compare multiple rows; you just need to refer to a non-deterministic function in the connect by clause:
create table t42 (id, old_string, new_string) as
select 1, 'Joe|Public|NY', 'Joe|Smith|NY' from dual
union all select 2, 'Joe|Public|NY', 'Joe|Smith|NY|USA' from dual;
with vals (id, pos, old_val, new_val) as (
select id, level,
regexp_substr(old_string, '(.*?)(\||$)', 1, level, null, 1),
regexp_substr(new_string, '(.*?)(\||$)', 1, level, null, 1)
from t42
connect by id = prior id
and prior dbms_random.value is not null
and level < greatest(regexp_count(old_string, '(.*?)(\||$)'),
regexp_count(new_string, '(.*?)(\||$)'))
)
select id, listagg(case when (old_val is null and new_val is null)
or old_val = new_val then '-' else new_val end, '|')
within group (order by pos) as diff
from vals
group by id
order by id;
ID DIFF
---------- --------------------
1 -|Smith|-
2 -|Smith|-|USA
If they are coming from different rows or different tables then it's more complicated and #MTOs approach should be considered.
I should also point out that I assumed the spaces around your delimiter were to make the strings easier to read in the question; if they are actually in the data then the pattern can be adjusted (like #MTO's, again).

It's not a simple solution but you can do it in SQL using collections and a hierarchical query that uses regular expressions to match each element of the delimited list.
(Note: This method will work with multiple input rows.)
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE table_name ( id, old_string, new_string ) AS
SELECT 1, 'Joe | Public | NY', 'Joe | Smith | NY' FROM DUAL UNION ALL
SELECT 2, 'Joe | Public | NY', 'Joe | Smith | NY|USA' FROM DUAL
/
CREATE TYPE indexed_string AS OBJECT(
idx INT,
value VARCHAR2(100)
)
/
CREATE TYPE indexed_string_table AS TABLE OF indexed_string
/
Query 1:
SELECT id,
( SELECT LISTAGG(
CASE
WHEN o.value = n.value THEN '-'
WHEN o.value IS NULL AND n.value IS NULL THEN '-'
ELSE n.value
END,
' | '
) WITHIN GROUP ( ORDER BY COALESCE( o.idx, n.idx ) )
FROM TABLE(
CAST(
MULTISET(
SELECT indexed_string(
LEVEL,
REGEXP_SUBSTR(
t.old_string,
'(.*?)($|\s*\|\s*)',
1,
LEVEL,
NULL,
1
)
)
FROM DUAL
CONNECT BY LEVEL < REGEXP_COUNT(
t.old_string,
'(.*?)($|\s*\|\s*)'
)
) AS indexed_string_table
)
) o
FULL OUTER JOIN
TABLE(
CAST(
MULTISET(
SELECT indexed_string(
LEVEL,
REGEXP_SUBSTR(
t.new_string,
'(.*?)($|\s*\|\s*)',
1,
LEVEL,
NULL,
1
)
)
FROM DUAL
CONNECT BY LEVEL < REGEXP_COUNT(
t.new_string,
'(.*?)($|\s*\|\s*)'
)
) AS indexed_string_table
)
) n
ON ( o.idx = n.idx )
) AS changes
FROM table_name t
Results:
| ID | CHANGES |
|----|---------------------|
| 1 | - | Smith | - |
| 2 | - | Smith | - | USA |

Related

SQL to find upper case words from a column

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.

Select query where a column contains set of numbers

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.

Passing regexp_replace matches into arguments

I've got a set of strings in a database which describe calculations like this:
"#id1# = #id2# + #id3#"
and a table with the ids like this:
ID Human_friendly_name
id1 Name1
id2 Name2
id3 Name3
I'd like to substitute the human-friendly names in for the #id# format, giving me a result of:
Name1 = Name2 + Name3
The calculations do not have a limit on how many variables they can include - some are in the hundreds
One potential way to do this would be to split the equation into multiple rows (using a recursive trim, for example), do a lookup for the names and then use LISTAGG to recombine the strings. But that seems overly complicated.
What I'd really like to do is use REGEXP_REPLACE to pass the matches into the argument for the replacement string, i.e.:
REGEXP_REPLACE('My calculation string',
'#\d+#',
(select max(name) from table where id = REGEX_MATCH)
)
I haven't been able to find any examples of passing the matched value into the replacement_string argument (although the SELECT part works). Can anyone tell me how to do this, or confirm that it's impossible?
Thought about it some more... Perhaps you meant something different?
Do you have a table with strings, like '#id1# = #id2# + #id3#', and you are looking for a query that will substitute 'Name1' in place of '#id1#', etc. - that is, the + sign in the string has NO meaning whatsoever, and you are simply wanting to do a string replacement based on a substitution table? So, for example, if you had another string '#id1# is better than a glass of #id2#' you would want the output 'Name1 is better than a glass of Name2'?
If so, you will need regular expressions AND a recursive process of some sort. Below I show how this can be done in Oracle versions 11.2 and higher (since I use the recursive subquery factoring introduced in 11.2).
Input tables:
Table: INPUT_STRINGS
Columns: INP_STR
INP_STR
------------------------------------------------------------
#id1# = #id2# + #id3# + #id1# / #id3#
Let #id2# be equal to #id4# - 5 + #id1##id2#
Table: HUMAN_READABLE
Columns: ID, HUMAN_READABLE_NAME
ID HUMAN_READABLE_NAME
-------------------- -----------------------------
id1 James Bond
id2 cat$5FISH
id3
id4 star
Query:
with t (input_str, temp_str, ct) as (
select inp_str, inp_str, regexp_count(inp_str, '#') from input_strings
union all
select input_str, regexp_replace(temp_str, '#[^#]*#',
(select human_readable_name from human_readable
where regexp_substr(temp_str, '#[^#]*#') = '#'||id||'#'), 1, 1), ct - 2
from t
where ct != 0
)
select t.input_str, temp_str as human_readable_str from t where ct = 0;
Output:
INPUT_STR HUMAN_READABLE_STR
-------------------------------------------- ------------------------------------------------------------
Let #id2# be equal to #id4# - 5 + #id1##id2# Let cat$5FISH be equal to star - 5 + James Bondcat$5FISH
#id1# = #id2# + #id3# + #id1# / #id3# James Bond = cat$5FISH + + James Bond /
Interesting problem. I think the issue is with when Oracle evaluates the backreferences in regexp_replace (so instead of sending the value of \1 you are actually sending the literal string '\1'). Anyway, here is a solution using SQL modeling (I like mathguy's answer too, this is just a different approach):
First, setup your ref table holding the id=>name translations:
create table my_ref
(
id varchar2(50) not null primary key,
name varchar2(50)
);
insert into my_ref (id, name) values ('id1','name1');
insert into my_ref (id, name) values ('id2','name2');
insert into my_ref (id, name) values ('id3','name3');
insert into my_ref (id, name) values ('id4','name4');
insert into my_ref (id, name) values ('id5','name5');
insert into my_ref (id, name) values ('id6','name6');
commit;
And the main table with a few example entries:
create table my_tab
(
formula varchar2(50)
);
insert into my_tab values ('#id1# = #id2# + #id3#');
insert into my_tab values ('#test# = some val #id4#');
commit;
Next, a basic function to translate a single id to name (lookup function):
create or replace function my_ref_fn(i_id in varchar2)
return varchar2
as
rv my_ref.name%type;
begin
begin
select
-- replace id with name, keeping spaces
regexp_replace(i_id, '( *)#(.+)#( *)', '\1' || name || '\3')
into rv
from my_ref
where id = ltrim(rtrim(trim(i_id), '#'),'#');
exception
when no_data_found then null;
end;
dbms_output.put_line('"' || i_id || '" => "' || rv || '"');
return rv;
end;
And to use it, we need to use SQL modeling:
select formula, new_val
from my_tab
MODEL
PARTITION BY (ROWNUM rn)
DIMENSION BY (0 dim)
MEASURES(formula, CAST('' AS VARCHAR2(255)) word, CAST('' AS VARCHAR(255)) new_val)
RULES ITERATE(99) UNTIL (word[0] IS NULL)
(word[0] = REGEXP_SUBSTR(formula[0], '( *)[^ ]+( *|$)', 1, ITERATION_NUMBER + 1)
, new_val[0] = new_val[0] || nvl(my_ref_fn(word[0]), word[0])
);
Which gives:
FORMULA;NEW_VAL
"#id1# = #id2# + #id3#";"name1 = name2 + name3"
"#test# = some val #id4#";"#test# = some val name4"

Oracle SQL: Using CASE in a SELECT statement with Substr

In a SELECT statement would it be possible to evaluate a Substr using CASE? Or what would be the best way to return a sub string based on a condition?
I am trying to retrieve a name from an event description column of a table. The string in the event description column is formatted either like text text (Mike Smith) text text or text text (Joe Schmit (Manager)) text text. I would like to return the name only, but having some of the names followed by (Manager) is throwing off my SELECT statement.
This is my SELECT statement:
SELECT *
FROM (
SELECT Substr(Substr(eventdes,Instr(eventdes,'(')+1),1,
Instr(eventdes,')') - Instr(eventdes,'(')-1)
FROM mytable
WHERE admintype = 'admin'
AND entrytime BETWEEN sysdate - (5/(24*60)) AND sysdate
AND eventdes LIKE '%action taken by%'
ORDER BY id DESC
)
WHERE ROWNUM <=1
This returns things like Mike Smith if there is no (Manager), but returns things like Joe Schmit (Manager if there is.
Any help would be greatly appreciated.
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE MYTABLE ( id, admintype, entrytime, eventdes ) AS
SELECT 1, 'admin', SYSDATE, 'action taken by (John Doe (Manager)) more text' FROM DUAL;
Query 1:
SELECT *
FROM ( SELECT REGEXP_SUBSTR( eventdes, '\((.*?)(\s*\(.*?\))?\)', 1, 1, 'i', 1 )
FROM mytable
WHERE admintype = 'admin'
AND entrytime BETWEEN sysdate - (5/(24*60)) AND sysdate
AND eventdes LIKE '%action taken by%'
ORDER BY id DESC
)
WHERE ROWNUM <=1
Results:
| REGEXP_SUBSTR(EVENTDES,'\((.*?)(\S*\(.*?\))?\)',1,1,'I',1) |
|------------------------------------------------------------|
| John Doe |
Edit:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE MYTABLE ( id, admintype, entrytime, eventdes ) AS
SELECT 1, 'admin', SYSDATE, 'action taken by (Doe, John (Manager)) more text' FROM DUAL;
Query 1:
SELECT SUBSTR( Name, INSTR( Name, ',' ) + 1 ) || ' ' || SUBSTR( Name, 1, INSTR( Name, ',' ) - 1 ) AS Full_Name,
REGEXP_REPLACE( Name, '^(.*?),\s*(.*)$', '\2 \1' ) AS Full_Name2
FROM ( SELECT REGEXP_SUBSTR( eventdes, '\((.*?)(\s*\(.*?\))?\)', 1, 1, 'i', 1 ) AS Name
FROM mytable
WHERE admintype = 'admin'
-- AND entrytime BETWEEN sysdate - (5/(24*60)) AND sysdate
AND eventdes LIKE '%action taken by%'
ORDER BY id DESC
)
WHERE ROWNUM <=1
Results:
| FULL_NAME | FULL_NAME2 |
|-----------|------------|
| John Doe | John Doe |
You could use INSTR to search the last ')', but I would prefer a Regular Expression.
This extracts everything between the first '(' and the last ')' and the TRIMs remove the brackets (Oracle doesn't support look around in RegEx):
RTRIM(LTRIM(REGEXP_SUBSTR(eventdes, '\(.*\)'), '('), ')')
There is probably a regex that would do this without a replace but I am not smart enough to work it out so I combined a REGEXP_SUBSTRING that replaces your multiple instr methods and a REGEXP_REPLACE to replace the ' (MANAGER)' portion is it exists.
SELECT *
FROM (
SELECT REGEXP_REPLACE(
REGEXP_SUBSTR(eventdes, '\((.*)\)', 1, 1, 'i', 1), ' \(MANAGER\)', '')
FROM mytable
WHERE admintype = 'admin'
AND entrytime BETWEEN sysdate - (5/(24*60)) AND sysdate
AND eventdes LIKE '%action taken by%'
ORDER BY id DESC
)
WHERE ROWNUM <=1
Here's a slightly different take on the problem. This regexp starts at the position of the first open paren + 1 (the first letter of the name), then returns the first group from that point in the string on that consists of 1 or more characters that is not a close paren or an open paren. The TRIM gets rid of the trailing space if there is a manager.
SQL> with tbl(id, admintype, entrytime, eventdes ) AS
2 ( SELECT 1, 'admin', SYSDATE, 'action taken by (John Doe (Manager)) more text' FROM DUAL
3 union
4 SELECT 1, 'admin', SYSDATE, 'action taken by (Jane Doe) more text' FROM DUAL
5 )
6 select trim(regexp_substr(eventdes, '([^\)\(]+)', instr(eventdes, '(')+1, 1)) name
7 from tbl;
NAME
----------------------------------------------
Jane Doe
John Doe
SQL>

Reg Expression in oracle?

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;