Substring Function - sql

The output from a column is like this 'Arpit-Bansal-Doctor-ALC FU JP-111DC'.I want to extract the data which is between the - - that is for example Bansal.
Please advice which function to use and how to use.

You can do it using the built-in functions INSTR and SUBSTR like this:
select substr(col,instr(col,'-')+1,instr(col,'-',1,2)-instr(col,'-')-1)
from (select 'Arpit-Bansal-Doctor-ALC FU JP-111DC' col from dual);
The INSTR function calls are used to find the first and second hyphen, and then SUBSTR is used to get the string between them.
This is quite cumbersome, so if you want all 5 values separated out and you are in PL/SQL you can use apex_util.string_to_table to separate them:
declare
array apex_application_global.vc_arr2;
begin
array := apex_util.string_to_table ('Arpit-Bansal-Doctor-ALC FU JP-111DC', '-');
for i in 1..array.count loop
dbms_output.put_Line ('Part' || i || ' is '||array(i));
end loop;
end;

Another method:
select regexp_substr('Arpit-Bansal-Doctor-ALC FU JP-111DC', '[^-]+')
from dual;
Arpit
select regexp_substr('Arpit-Bansal-Doctor-ALC FU JP-111DC', '[^-]+', 1, 2)
from dual;
Bansal
select regexp_substr('Arpit-Bansal-Doctor-ALC FU JP-111DC', '[^-]+', 1, 3)
from dual;
Doctor
select regexp_substr('Arpit-Bansal-Doctor-ALC FU JP-111DC', '[^-]+', 1, 4)
from dual;
ALC FU JP

Related

Oracle replace a word in the middle

fse.name = ASD-122-XXX and fn.name = ASD-125-FFF
I want to put all fn.name the 122 of the fse.name associate to the fse.name
I have this but it doesn't work the way I want
var_rama VARCHAR2;
Begin
FOR var in (select fse.name ,fn.name as nombreFnode, fn.descr
from fiberspliceenclosure_lt fse
inner join segments seg
on fse.mslink = seg.link1
inner join fibernode fn
on fn.mslink = seg.link2
where fse.name like 'CO905%'
and fn.descr like '04-%')
LOOP
var.(substr(fse.name,7,5)) := var_rama;
replace (substr (fn.name,7,5), var_rama);
END LOOP;
If your names are always in the same format i.e. AAA-999-AAA this is fairly simple with just substr() calls:
select substr(fn.name, 1, 4)
|| substr(fse.name, 5, 3)
|| substr(fn.name, 8) as new_name
from fn
join fse on fn.id = fse.id
/
However, if the name formats can vary - say AAAA-99-AAAAA is valid - then we can't use fixed offsets. So the code becomes more complicated, with instr() calls to identify the position of each dash:
select substr(fn.name, 1, instr(fn.name, '-', 1, 1))
|| substr(fse.name, instr(fse.name, '-', 1, 1)+1, instr(fse.name, '-', 1, 2) - instr(fse.name, '-', 1, 1)-1 )
|| substr(fn.name, instr(fn.name, '-', 1, 2)) as new_name
from fn
join fse on fn.id = fse.id
/
No doubt there is a snazzier solution using regular expressions but I prefer to avoid regex for tasks which can be done with substr() and instr() for performance reasons.
Why dont you try with INSTR function to find out whether your finding string is there in you destination variable or not? if its there then just use REPLACE to replace the string with your desire string. I guess, this would work for you..because every time you dont need to find the position of your search string.
As per my understanding, he want to replace 122 from fse.name with fn.name string. so below is my code for that.
Declare
v_instr number;
v_out varchar2(50);
begin
for i in (sql_query) loop
v_instr := instr(i.fse_name,'122');
if v_instr >0 then
v_out := replace(i.fse_name,'122',i.fn_name);
end if;
end loop;
end;

Convert delimited string to rows in oracle [duplicate]

This question already has answers here:
How to convert comma separated values to rows in oracle?
(6 answers)
Closed 4 years ago.
I used to use below query to convert comma delimited string to rows -
select regexp_substr('A,B,C,D','[^,]+', 1, level) from dual
connect by regexp_substr('A,B,C,D', '[^,]+', 1, level) is not null;
But, now my delimiter is - '~^'
I am not able to use same query for this delimiter.
select regexp_substr('A~^B~^C~D^E','[^~^]+', 1, level) from dual
connect by regexp_substr('A~^B~^C~D^E', '[^~^]+', 1, level) is not null;
I expect -
Column
A
B
C~D^E
Please help
OR May be is it possible to get nth element if delimiter provided is ~^
Best Regards
Riffing off this method for splitting a string while allowing for nulls:
select regexp_substr('A~^B~^C~D^E','(.*?)(~\^|$)', 1, level, null, 1) from dual
connect by level < regexp_count('A~^B~^C~D^E','(.*?)(~\^|$)');
REGEXP_SUBS
-----------
A
B
C~D^E
As in the linked answer it looks for any characters, non-greedily, followed by the combination of ~^ (with the caret escaped, so ~\^) or end-of-line. The regexp_substr() calls also uses the optional arguments to specify subexpr - so it only gets the first grouping (.*?) and not the delimiter itself, which is in the second grouping.
If you want a specific element then that's even closer to the linked post:
select regexp_substr('A~^B~^C~D^E','(.*?)(~\^|$)', 1, 3, null, 1) from dual;
REGEX
-----
C~D^E
Or as you're doing this in a procedure, use the connect-by query to populate a collection, and then pick out the element you need, if you'll be looking at more than one.
I can't do it with regexp functions - too hard! How about this bespoke function?
create or replace function test (p_str varchar2, p_delim varchar2)
return SYS.KU$_VCNT -- handy table of VARCHAR2(4000) in SYS
is
l_str long := p_str;
l_ret SYS.KU$_VCNT := SYS.KU$_VCNT();
begin
while instr (l_str, p_delim) > 0 loop
l_ret.extend;
l_ret(l_ret.count) := substr(l_str, 1, instr (l_str, p_delim)-1);
l_str := substr (l_str, instr (l_str, p_delim)+2);
end loop;
if l_str is not null then
l_ret.extend;
l_ret(l_ret.count) := l_str;
end if;
return l_ret;
end;
Then:
select * from table (test('A~^B~^C~D^E','~^'));
A
B
C~D^E
nth element (e.g. 2nd):
select column_value from
(select column_value, rownum n from table (test('A~^B~^C~D^E','~^')))
where n = 2;
Hmmm . . . This comes very close:
select regexp_substr('A~^B~^C~D^E', '([^~]|[~][^^])+', 1, level) from dual
connect by regexp_substr('A~^B~^C~D^E', '([^~]|[~][^^])+', 1, level) is not null
You can get rid of the ^ by doing:
select regexp_replace(regexp_substr('A~^B~^C~D^E', '([^~]|[~][^^])+', 1, level), '^\^', '') from dual
connect by regexp_substr('A~^B~^C~D^E', '([^~]|[~][^^])+', 1, level) is not null

separate values from a string using SQL

I have this requirement where we need to separate values from a string the format of the is like
{Feature1=Value1} | {Feature2=Value2} | .. | {FeatureN=ValueN}
{12345=Gold}|{12346=Silver}
so need to separate features and values from the given srting..
To separate PIPE separated values i am using..
select *
from xmltable('r/c' passing xmltype('<r><c>' || replace('{12345=Gold}|{12346=Silver}','|','</c><c>') || '</c></r>')
columns new_str varchar2(30) path '.');
NEW_STR
------------------------------
{12345=Gold}
{12346=Silver}
I am writing a PLSQL block which iterate through each pipe separate values using above query.
I can store these values in PLSQL variable.
Now another task here is to get features and values from two above strings for this i write below SQL
select substr ('{12345=Gold}',2, instr('{12345=Gold}', '=')-2) features from dual;
FEATURES
----------------------
12345
SELECT SUBSTR('{12345=Gold}', instr('{12345=Gold}', '=')+1, LENGTH(substr ('{12345=Gold}', instr('{12345=Gold}', '=')+1, INSTR('{12345=Gold}', '}', 2)))-1) value FROM DUAL;
VALUE
--------------
Gold
So here i am able to get the features and values from a string......
I am looking for another or alternate SQL for my SQL's specially for the last one i find it complex function use so if you have any better idea for the above scenario then please Post !
Please ask if the scenario is not clear
MY DB is --
Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
with line as (
select '{12345=Gold}|{12346=Silver}|{12399=Copper}' str from dual)
select substr (parse, 2, instr(parse,'=')-2) as feature
,substr (parse, instr(parse,'=')+1, length(parse)-instr(parse,'=')-1 ) as value
from
(select distinct regexp_substr(str, '[^|]+', 1, level) as parse
from line
connect by regexp_substr(str, '[^|]+', 1, level) is not null)
Here is a way using types and a handy built-in package called apex_util:
create type keyval_t is object (key varchar2(10), value varchar2(100));
/
create type keyval_tab_t is table of keyval_t;
/
create or replace package test_pkg is
function keyval_tab (p_keyval_string varchar2) return keyval_tab_t;
end;
/
create or replace package body test_pkg is
function keyval_tab (p_keyval_string varchar2) return keyval_tab_t
is
l_tab apex_application_global.vc_arr2;
l_tab2 apex_application_global.vc_arr2;
l_keyval_tab keyval_tab_t := keyval_tab_t();
l_str long;
begin
-- Split string at pipe delimiters
l_tab := apex_util.string_to_table (p_keyval_string, '|');
-- For each {key=value} pair
for i in 1..l_tab.count loop
l_str := l_tab(i);
-- Remove the {}
l_str := ltrim (l_str, '{ ');
l_str := rtrim (l_str, '} ');
-- Split string into key and value
l_tab2 := apex_util.string_to_table (l_str, '=');
if l_tab2.count = 2 then
l_keyval_tab.extend;
l_keyval_tab(i) := keyval_t (l_tab2(1), l_tab2(2));
else
-- ?? invalid string
null;
end if;
end loop;
return l_keyval_tab;
end;
end;
/
Now you can query:
SQL> select value from table(test_pkg.keyval_tab ('{12345=Gold}|{12346=Silver}'))
2* where key='12346';
VALUE
--------------------------------------------------------------------------------
Silver
SQL> select key from table(test_pkg.keyval_tab ('{12345=Gold}|{12346=Silver}'))
2 where value='Gold';
KEY
----------
12345
Use this query to get your expected output. Sorry if there are too many replace functions. But this is quite easier.
select EXTRACTVALUE (COLUMN_VALUE, '/row/Code') code,
EXTRACTVALUE (COLUMN_VALUE, '/row/Value') Value
from TABLE(XMLSEQUENCE(EXTRACT(XMLTYPE('<rowset><row>'||replace(replace(replace(replace('{12345=Gold}|{12346=Silver}','}|{','</Value></row><row><Code>'),'{','<Code>'),'=','</Code><Value>'),'}','</Value>')||'</row></rowset>'),'/rowset/row')));
If you already have separated columns into rows the work to do is fairly simple with REGEXP_REPLACE function.
Considering that your cols now is:
NEW_STR
--------------
{12345=Gold}
{12346=Silver}
You can do this sql to transform it into two different columns:
select regexp_replace( col, '\{(\d+)=\w+\}', '\1' ) as feature,
regexp_replace( col, '\{\d+=(\w+)\}', '\1' ) as value
from testTable
Transform that into a view and then just select with columns as you like:
create or replace view testView as
select regexp_replace( col, '\{(\d+)=\w+\}', '\1' ) as feature,
regexp_replace( col, '\{\d+=(\w+)\}', '\1' ) as value
from testTable
Then just do:
select * from testView where feature = '12345'
Or
Select * from testView where value = 'Gold'
If you like to transform the feature value as a number just use the to_number function on that column as:
to_number(regexp_replace( col, '\{(\d+)=\w+\}', '\1' ))
Remember that in order to do this you must be absolute sure that it is only numbers on that, otherwise you will have conversion errors
You can also use a pivot table and REGEXP_SUBSTR
with MyStrings as
(select '{Feature1=Value1}|{Feature2=Value2}|{FeatureN=ValueN}' Str from dual
union all
select '{12345=Gold}|{12346=Silver}' from dual
)
,pivot as (
Select Rownum Pnum
From dual
Connect By Rownum <= 100
)
SELECT rownum rn
,REGEXP_SUBSTR (ms.Str,'[^|]+',1,pv.pnum) TXT
FROM MyStrings ms
,pivot pv
where REGEXP_SUBSTR (ms.Str,'[^|]+',1,pv.pnum) is not null

How do I expand a string with wildcards in PL/SQL using string functions

I have a column, which stores a 4 character long string with 4 or less wild characters (for eg. ????, ??01', 0??1 etc). For each such string like 0??1 I have to insert into another table values 0001 to 0991; for the string ??01, values will be be 0001 to 9901; for string ???? values will be 0000 to 9999 and so on.
How could I accomplish this using PL/SQL and string functions?
EDIT
The current code is:
declare
v_rule varchar2(50) := '????52132';
v_cc varchar2(50);
v_nat varchar2(50);
v_wild number;
n number;
begin
v_cc := substr(v_rule,1,4);
v_nat := substr(v_rule,5);
dbms_output.put_line (v_cc || ' '|| v_nat);
if instr(v_cc, '????') <> 0 then
v_wild := 4;
end if;
n := power(10,v_wild);
for i in 0 .. n - 1 loop
dbms_output.put_line(substr(lpad(to_char(i),v_wild,'0' ),0,4));
end loop;
end;
/
Would something like the following help?
BEGIN
FOR source_row IN (SELECT rule FROM some_table)
LOOP
INSERT INTO some_other_table (rule_match)
WITH numbers AS (SELECT LPAD(LEVEL - 1, 4, '0') AS num FROM DUAL CONNECT BY LEVEL <= 10000)
SELECT num FROM numbers WHERE num LIKE REPLACE(source_row.rule, '?', '_');
END LOOP;
END;
/
This assumes you have a table called some_table with a column rule, which contains text such as ??01, 0??1 and ????. It inserts into some_other_table all numbers from 0000 to 9999 that match these wild-carded patterns.
The subquery
SELECT LPAD(LEVEL - 1, 4, '0') AS num FROM DUAL CONNECT BY LEVEL <= 10000)
generates all numbers in the range 0000 to 9999. We then filter out from this list of numbers any that match this pattern, using LIKE. Note that _ is the single-character wildcard when using LIKE, not ?.
I set this up with the following data:
CREATE TABLE some_table (rule VARCHAR2(4));
INSERT INTO some_table (rule) VALUES ('??01');
INSERT INTO some_table (rule) VALUES ('0??1');
INSERT INTO some_table (rule) VALUES ('????');
COMMIT;
CREATE TABLE some_other_table (rule_match VARCHAR2(4));
After running the above PL/SQL block, the table some_other_table had 10200 rows in it, all the numbers that matched all three of the patterns given.
Replace * to %, ? to _ and use LIKE clause with resulting values.
To expand on #Oleg Dok's answer, which uses the little known fact that an underscore means the same as % but only for a single character and using PL\SQL I think the following is the simplest way to do it. A good description of how to use connect by is here.
declare
cursor c_min_max( Crule varchar2 ) is
select to_number(min(numb)) as min_n, to_number(max(numb)) as max_n
from ( select '0000' as numb
from dual
union
select lpad(level, 4, '0') as numb
from dual
connect by level <= 9999 )
where to_char(numb) like replace(Crule, '?', '_');
t_mm c_min_max%rowtype;
l_rule varchar2(4) := '?091';
begin
open c_min_max(l_rule);
fetch c_min_max
into t_mm;
close c_min_max;
for i in t_mm.min_n .. t_mm.max_n loop
dbms_output.put_line(lpad(i, 4, '0'));
end loop;
end;
/

How to reverse a string after tokenizing it in SQL

I need to tokenize a string and reverse it in SQL. For example if the string is, 'L3:L2:L1:L0', i need to reverse it as 'L0:L1:L2:L3'. The tokenizing could be done using a delimiter ':' and then reverse it. Please suggest a Function in SQL for the same.
Thanks in advance,
Geetha
If possible, the best solution would be to change your data so that each value is stored in a different row.
If that doesn't work, you can create a PL/SQL function.
If you want a purely SQL solution, typically you'll have to split each value into multiple rows (cross join with an object table, or connect by level <= max number of items), and then re-aggregate the data using one of a dozen different methods (listagg, collect, stragg, xml, sys_connect_by_path, etc.)
Another SQL-only way is to use regular expressions. This is probably the fastest, but it only works with up to 9 items because Oracle only supports 9 back references:
--Get everything except the extra ':' at the end.
select substr(string, 1, length(string) - 1) string from
(
select regexp_replace(
--Add a delimter to the end so all items are the same
'L3:L2:L1:L0'||':'
--Non-greedy search for anything up to a : (I bet there's a better way to do this)
,'(.*?:)?(.*?:)?(.*?:)?(.*?:)?(.*?:)?(.*?:)?(.*?:)?(.*?:)?(.*?:)?(.*?:)?'
--Reverse the back-references
,'\9\8\7\6\5\4\3\2\1') string
from dual
);
Something like :
SELECT
REGEXP_REPLACE('L1:L2:L3',
'([[:alnum:]]{1,}):([[:alnum:]]{1,}):([[:alnum:]]{1,})',
'\3 \2 \1') "REGEXP_REPLACE"
from dual
But you might need to detail what constitutes a token.
Here is a solution using a PL/SQL pipelined function to split the elements:
create type t_str_array as table of varchar2(4000);
create or replace function split_str (p_str in varchar2,
p_separator in varchar2 := ':') return t_str_array pipelined
as
l_str varchar2(32000) := p_str || p_separator;
l_pos pls_integer;
begin
loop
l_pos := instr(l_str, p_separator);
exit when (nvl(l_pos,0) = 0);
pipe row (ltrim(rtrim(substr(l_str,1,l_pos-1))));
l_str := substr(l_str, l_pos+1);
end loop;
return;
end split_str;
Then you would use normal SQL to order the elements:
select * from table(split_str('L3:L2:L1:L0')) order by column_value
declare
s varchar2(1000) := 'L 1 0:L9:L8:L7:L6:L5:L4:L3:L2:L1:L0';
j number := length(s);
begin
for i in reverse 1..length(s) loop
if substr(s, i, 1) = ':' then
dbms_output.put(substr(s, i + 1, j - i) || ':');
j := i - 1;
end if;
end loop;
dbms_output.put_line(substr(s, 1, j));
end;
Convert elements in a CSV string into records, suppressing all NULLs:
SELECT REGEXP_SUBSTR( :csv,'[^,]+', 1, LEVEL ) AS element
FROM dual
CONNECT BY REGEXP_SUBSTR( :csv, '[^,]+', 1, LEVEL ) IS NOT NULL ;
Convert elements in a CSV string into records, preserving NULLs (but not order):
SELECT REGEXP_SUBSTR( :csv,'[^,]+', 1, LEVEL ) AS element
FROM dual
CONNECT BY LEVEL <= LENGTH( :csv ) - LENGTH( REPLACE( :CSV, ',' ) ) + 1 ;
Improving upon Kevan's answer, here is what I tried:
select listagg(TOKEN, ':') WITHIN GROUP (ORDER BY TOKEN_LEVEL DESC)
from
(SELECT REGEXP_SUBSTR( myStr,'[^:]+', 1, LEVEL ) AS TOKEN, LEVEL TOKEN_LEVEL
FROM dual
CONNECT BY REGEXP_SUBSTR( myStr, '[^:]+', 1, LEVEL ) IS NOT NULL);
Since you use Oracle it would be easy to generate a java stored procedure passing the string and then
split sting into array
loop array backwards and concate the resulting string
return the resulting string
this will be a small java code and not slower then pl/sql. but if you want to use pl/sql you can possibly also use DBMS_UTILITY.table_to_comma/.comma_to_table. But as the function name let assume -> you have to use "," as token.