Split String by delimiter position using oracle SQL - sql

I have a string and I would like to split that string by delimiter at a certain position.
For example, my String is F/P/O and the result I am looking for is:
Therefore, I would like to separate the string by the furthest delimiter.
Note: some of my strings are F/O also for which my SQL below works fine and returns desired result.
The SQL I wrote is as follows:
SELECT Substr('F/P/O', 1, Instr('F/P/O', '/') - 1) part1,
Substr('F/P/O', Instr('F/P/O', '/') + 1) part2
FROM dual
and the result is:
Why is this happening and how can I fix it?

Therefore, I would like to separate the string by the furthest delimiter.
I know this is an old question, but this is a simple requirement for which SUBSTR and INSTR would suffice. REGEXP are still slower and CPU intensive operations than the old subtsr and instr functions.
SQL> WITH DATA AS
2 ( SELECT 'F/P/O' str FROM dual
3 )
4 SELECT SUBSTR(str, 1, Instr(str, '/', -1, 1) -1) part1,
5 SUBSTR(str, Instr(str, '/', -1, 1) +1) part2
6 FROM DATA
7 /
PART1 PART2
----- -----
F/P O
As you said you want the furthest delimiter, it would mean the first delimiter from the reverse.
You approach was fine, but you were missing the start_position in INSTR. If the start_position is negative, the INSTR function counts back start_position number of characters from the end of string and then searches towards the beginning of string.

You want to use regexp_substr() for this. This should work for your example:
select regexp_substr(val, '[^/]+/[^/]+', 1, 1) as part1,
regexp_substr(val, '[^/]+$', 1, 1) as part2
from (select 'F/P/O' as val from dual) t
Here, by the way, is the SQL Fiddle.
Oops. I missed the part of the question where it says the last delimiter. For that, we can use regex_replace() for the first part:
select regexp_replace(val, '/[^/]+$', '', 1, 1) as part1,
regexp_substr(val, '[^/]+$', 1, 1) as part2
from (select 'F/P/O' as val from dual) t
And here is this corresponding SQL Fiddle.

Related

Oracle - How to extract delimited string

I have a sample String as below,
A|SDFR|RESTA|PRET|PRUQA
B|121|BBCTRI|9ALFA|DEV|5AS
I want to extract the part that is coming after send delimiter,
Expected,
RESTA|PRET|PRUQA
BBCTRI|9ALFA|DEV|5AS
What i got is just extracting single characters regexp_substr
Assuming you mean after the second delimiter, you don't need to use regular expressions for this; you can use the basic ]substr()](http://docs.oracle.com/database/121/SQLRF/functions196.htm) function, getting the starting position with instr():
substr(<your_string>, instr(<your_string>, '|', 1, 2) + 1)
The third argument to instr() says you want the second occurrence; the second argument says you're starting from position 1. That then points to the second delimiter, and you want to start at the next character after that delimiter, so have to add 1.
Demo:
with t (str) as (
select 'A|SDFR|RESTA|PRET|PRUQA' from dual
union all select 'B|121|BBCTRI|9ALFA|DEV|5AS' from dual
)
select substr(str, instr(str, '|', 1, 2) + 1)
from t;
SUBSTR(STR,INSTR(STR,'|',1
--------------------------
RESTA|PRET|PRUQA
BBCTRI|9ALFA|DEV|5AS
try this:
substr(string, instr(string, '|', 1, 2)+1)
Maybe like this;
with a as (select 'B|121|BBCTRI|9ALFA|DEV|5AS' test from dual)
select substr(test,instr(test,'|',3)+1) from a

Oracle sql : get only specific part of a substring

I'm struggling with a query in Oracle SQL, wanting to get some timings out of some text stored in an Oracle db.
Table :
kde_test (myString varchar(50))
Table contents (3 records):
'task1 - 6m'
'task2 - 66m'
'task3 - 666m'
I would like to get only the interesting part of the string, being the timings, so I would like to get only '6', '66' & '666' as results.
Searched this forum a bit, and got up with this query eventually, but it seems I do not completely get it, as the results it returns are :
6m
66m
666m
select
CASE
WHEN myString like 'task1%' THEN substr(myString,9,INSTR(myString,'m',1,1)-1)
WHEN myString like 'task2%' THEN substr(myString,9,INSTR(myString,'m',1,1)-1)
WHEN myString like 'task3%' THEN substr(myString,9,INSTR(myString,'m',1,1)-1)
END
from kde_test
where myString like 'task%'
EDIT :
Since some solutions (thanks already for quick response) take into account the specific values (eg. all 3 records ending on '6m'), maybe it best to take into account the values could be :
Table contents (3 records):
'task1 - 6m'
'task2 - 58m'
'task3 - 123m'
you can use this way too
select regexp_replace(SUBSTR('task3 - 666m' ,
INSTR('task3 - 666m', '-',1, 1)+1, length('task3 - 666m')), '[A-Za-z]')
from dual
result :666
Use SUBSTR and INSTR and make it dynamic.
SUBSTR(str,
instr(str, ' - ', 1, 1) +3,
instr(str, 'm', 1, 1) -
instr(str, ' - ', 1, 1) -3
)
For example,
SQL> WITH DATA AS(
2 SELECT 'task1 - 6m' str FROM dual UNION ALL
3 SELECT 'task2 - 66m' str FROM dual UNION ALL
4 SELECT 'task3 - 666m' str FROM dual UNION ALL
5 SELECT 'task4 - 58m' str FROM dual UNION ALL
6 SELECT 'task5 - 123m' str FROM dual
7 )
8 SELECT str,
9 SUBSTR(str, instr(str, ' - ', 1, 1) +3,
10 instr(str, 'm', 1, 1) - instr(str, ' - ', 1, 1) -3) new_st
11 FROM DATA;
STR NEW_STR
------------ ------------
task1 - 6m 6
task2 - 66m 66
task3 - 666m 666
task4 - 58m 58
task5 - 123m 123
SQL>
You can use the regex_substr function. \d+ means one or more digits, and $ anchors the end of the string.
select regexp_substr(str, '\d+m$')
from mytable
Example at SQL Fiddle.
In order to correct your current query, you should change the following string - "INSTR(myString,'m',1,1)-1" to "INSTR(myString,'m',1,1)-9".
However, the other answers provided above seem like a more elegant solution to your problem.
I did feel the need to publish this just to clarify what wasn't working well in current query - in INSTR function returns the position of the m letter, and then used as the length of the string to print. What my fix does is telling the query to print everything from the 9th character until the position of the m letter, which results in the task time required.
I have tried to divide this into two parts
First pick the string after -
regexp_substr ('task1 - 1234m', '[^ _ ]+',1, 3) --results 1234m
Fetch the number part of the string fetched from output of first
regexp_substr(regexp_substr ('task1 - 1234m', '[^ _ ]+',1, 3),'[[:digit:]]*')
--output 1234
So,the final query is
SELECT regexp_substr(regexp_substr (mystring, '[^ _ ]+',1, 3),'[[:digit:]]*')
FROM kde_test;
Use This:-
select substr(replace(myString,'m',''),9) output
from kde_test
where myString like 'task%'

How to replace more than one character in oracle?

How to replace multiple whole characters, except those in combinations...?
The below code replaces multiple characters, but it also disturbing those in combinations.
SELECT regexp_replace('a,ca,va,ea,r,y,q,b,g','(a|y|q|g)','X') RESULT FROM dual;
Current output:
RESULT
--------------------
X,cX,vX,eX,r,X,X,b,X
Expected output:
RESULT
------------------------
'X,ca,va,ea,r,X,X,b,X
I just want to replace only separate whole characters('a','y','q','g'), but not the 1 in combinations('ca','va','ea')...
Because you are delimiting with a comma ',' you can combine that like ',a,'
and this will replace only single a's.
you can try follows:
with t as
(
select 'a,ca,va,ea,r,y,q,b,g' str
from dual
)
select substr(sys_connect_by_path(regexp_replace(regexp_substr(str, '[^,]+', 1, level), '^(a|y|q|g)$', 'X'), ','), 2) as str
from t
where connect_by_isleaf = 1
connect by level <= length(regexp_replace(str, '[^,]*')) + 1;
Sadly oracle doesn´t support lookahead and lookbehind. But this is a solution i came up with.
SELECT regexp_replace
(regexp_replace
('a,ca,va,ea,r,y,q,b,g',
'^[ayqg](,)|(,)[ayqg](,)|(,)[ayqg]$',
'\2\4X\1\3'),'(,)[ayqg](,)','\1X\2')
RESULT FROM dual;
I had to use the regexp twice sadly, since it doesn´t find two similar values following after each other and replacing it. ..,a,y,.. is getting replaced as ..,X,y,... So the second call replaces the missing [ayqg] with the exact values. In the first inner regexp call replaces the first and last values.
Maybe this could be simplified into one expression, but i am not that conform with the regex from oracle.
As a explanation i am grouping the commata and basicly replace every ,[ayqg], with ,X, by backreferencing the commata
You would look for word boundaries, which is \b, and which is unfortunately not supported by Oracle's regexp_replace.
So let's look for a non-word character \W or the beginning ^ or ending $ of the text.
select
regexp_replace('a,ca,va,ea,r,y,q,b,g','(^|$|\W)(a|y|q|g)(^|$|\W)','\1X\3') as result
from dual;
In order to not remove the non-word characters, we must have them in the replace string: \1 for the expression in the first parenteses, \3 for the ones in the third. Thus we only change the expression in the second parentheses, which is a, y, q or g, with X.
Unfortunately above gives
X,ca,va,ea,r,X,q,b,X
The q was not replaced, because we recognize ',y,' thus being positioned a 'g,' whereas we'd need to be positioned at ',g,' to recognize g as a word, too.
So we need to replace in iterations (i.e. recursively):
with results(txt, num) as
(
select 'a,ca,va,ea,r,y,q,b,g' as txt, 0 as num from dual
union all
select regexp_replace(txt, '(^|$|\W)(a|y|q|g)(^|$|\W)','\1X\3'), num + 1 as num
from results
where txt <> regexp_replace(txt, '(^|$|\W)(a|y|q|g)(^|$|\W)','\1X\3')
)
select max(txt) keep (dense_rank last order by num) as result
from results;
EDIT: Kevin Esche is right; of course one has to do it only twice. Hence you can also do:
select
regexp_replace(txt, search_str, replace_str) as result
from
(
select
regexp_replace(txt, search_str, replace_str) as txt, search_str, replace_str
from
(
select
'a,ca,va,ea,r,y,q,y,q,b,g' as txt,
'(^|$|\W)(a|y|q|g)(^|$|\W)' as search_str,
'\1X\3' as replace_str
from dual
)
);
with replaced_values as (
SELECT case when length(val)=1 then regexp_replace(val,'(a|y|q|g)','X') else val end new_val, lvl
from (
SELECT regexp_substr('a,ca,va,ea,r,y,q,b,g','[^,]+', 1, LEVEL) val, level lvl FROM dual
connect by regexp_substr('a,ca,va,ea,r,y,q,b,g','[^,]+',1, LEVEL) is not null
) all_values
)
select lISTAGG(new_val, ',') WITHIN GROUP (ORDER BY lvl) RESULT
from replaced_values
This statement pivots data into rows and replaces only lines wich contains one character.
Data are then unpivoted in one rows
This sql works also with empty entries like 'a,,,b,c' and more complex regular expressions:
with t as
(select ',a,,ca,va,ea,bbb,ba,r,y,q,b,g,,,' as str,
',' as delimiter,
'(a|y|q|g|ea|[b]*)' as regexp_expr,
'X' as replace_expr
from dual)
(select substr (sys_connect_by_path(regexp_replace(substr(str,
decode(level - 1, 0, 0, instr(str, ',', 1, level - 1)) + 1,
decode(instr(str, ',', 1, level),
0,
length(str),
instr(str, ',', 1, level) - 1) -
decode(level - 1, 0, 0, instr(str, ',', 1, level - 1))),
'^' || regexp_expr || '$',
replace_expr), ','), 2)
from t
where connect_by_isleaf = 1
connect by level <= length(regexp_replace(str, '[^'|| delimiter||']')) + 1)
Result
,X,,ca,va,X,X,ba,r,X,X,X,X,,,
Don't Know much Oracle, but I would have thought something like this could work. Assuming the delimiter is always a comma.
SELECT
regexp_replace(regexp_replace(regexp_replace(regexp_replace(regexp_replace('a,ca,va,ea,r,y,q,b,g','(,a,|,y,|,q,|,g,)',',X,') ,'(,a,|,y,|,q,|,g,)',',X,'), '(^a,|^y,|^q,|^g,)','X,'), '(,a$|,y$|,q$|,g$)',',X'), '(^a$|^y$|^q$|^g$)','X')
RESULT FROM test;
The first two parts replaces a single character in commas in the middle, the third part gets those at the start of the string, the fourth is for the end of the string and the fifth is for when then string has just one character.
This answer might will be simplifiable by advanced Regexp use.
How i can replace words?
RS & OS ===> D, LS & IS ==== >
SECTION_ID Output required
1-LS-1991 1-P-1991
1-IS-1991 1-P-1991
1-RS-1991 1- D- 1991
1-OS-1991 1-D-1991

Splitting a string with regex whitespace delimitor

I have a string with this delimitor ' - ' (hyphen surrounded by spaces).
I have no problem getting each part of the string:
select regexp_substr(val, '[^-]+', 1, 1),
regexp_substr(val, '[^-]+', 1, 2)
from (select 'first - second' as val from dual) t
However I would like to exclude spaces between each term and the hyphen. I tried this:
regexp_substr(val, '[^\s-]+', 1, 1)
regexp_substr(val, '[^-\s]+', 1, 2)
But it doesn't seem to work. THe first expression returns fir,
1) I wonder why?
Here is the link to Fiddle:
http://www.sqlfiddle.com/#!4/d41d8/41570
2) How can I solve this?
If you want to split your string so you don't need Negated Character Classes,you can split with following pattern:
'[\s-]+'
And if you just want to find the words you can use :
\b[a-zA-Z]+\b
If you want to remove spaces from the beginning and end, then I think the simplest approach is to use trim():
select trim(regexp_substr(val, '[^-]+', 1, 1)),
trim(regexp_substr(val, '[^-]+', 1, 2))
from (select 'first - second' as val from dual) t
Capture your target in a group and have the function return that:
select regexp_substr(val, '(.*) *- *', 1, 1, '', 1),
regexp_substr(val, ' *- *(.*)', 1, 1, '', 1)
from (select 'first - second' as val from dual) t
See SQLFiddle
This allows you to express more precisely your delimiter. This technique is the next best thing to using a look ahead expression, which would be the approach most favoured in regex engines that support it and which Oracle does not support.
To explain what's going on, the target is captured using brackets and it returned using a back reference - the last parameter - naming group 1 (the first group of the expression) to be returned.
FYI group 0 is the whole match.

oracle 12c - select string after last occurrence of a character

I have below string:
ThisSentence.ShouldBe.SplitAfterLastPeriod.Sentence
So I want to select Sentence since it is the string after the last period. How can I do this?
Just for completeness' sake, here's a solution using regular expressions (not very complicated IMHO :-) ):
select regexp_substr(
'ThisSentence.ShouldBe.SplitAfterLastPeriod.Sentence',
'[^.]+$')
from dual
The regex
uses a negated character class to match anything except for a dot [^.]
adds a quantifier + to match one or more of these
uses an anchor $ to restrict matches to the end of the string
You can probably do this with complicated regular expressions. I like the following method:
select substr(str, - instr(reverse(str), '.') + 1)
Nothing like testing to see that this doesn't work when the string is at the end. Something about - 0 = 0. Here is an improvement:
select (case when str like '%.' then ''
else substr(str, - instr(reverse(str), ';') + 1)
end)
EDIT:
Your example works, both when I run it on my local Oracle and in SQL Fiddle.
I am running this code:
select (case when str like '%.' then ''
else substr(str, - instr(reverse(str), '.') + 1)
end)
from (select 'ThisSentence.ShouldBe.SplitAfterLastPeriod.Sentence' as str from dual) t
And yet another way.
Not sure from a performance standpoint which would be best...
The difference here is that we use -1 to count backwards to find the last . when doing the instr.
With CTE as
(Select 'ThisSentence.ShouldBe.SplitAfterLastPeriod.Sentence' str, length('ThisSentence.ShouldBe.SplitAfterLastPeriod.Sentence') len from dual)
Select substr(str,instr(str,'.',-1)+1,len-instr(str,'.',-1)+1) from cte;
select
substr(
'ThisSentence.ShouldBe.SplitAfterLastPeriod.Sentence',
INSTR(
'ThisSentence.ShouldBe.SplitAfterLastPeriod.Sentence',
'.',
-1
)+1
)
from dual;
The INSTR function accepts a third parameter, the occurrence. It defaults to 1 (the first occurrence), but also accepts negative numbers (meaning counting from the last occurrence backwards).
select substr(str, instr(str, '.', -1) + 1)
from (
select 'ThisSentence.ShouldBe.SplitAfterLastPeriod.Sentence'
as str
from dual);
Sentence
how many dots in a string?
select length(str) - length(replace(str, '.', '') number_of_dots from ...
get substring after last dot:
select substr(str, instr(str, '.', 1, number_of_dots)+1) from ...