SUBSTR to ADD value in oracle - sql

I have table with column having data in below format in Oracle DB.
COL 1
abc,mno:EMP
xyz:EMP;tyu,opr:PROF
abc,mno:EMP;tyu,opr:PROF
I am trying to convert the data in below format
COL 1
abc:EMP;mno:EMP
xyz:EMP;tyu:PROF;opr:PROF
abc:EMP;mno:EMP;tyu:PROF;opr:PROF
Basically trying to get everything after : and before ; to move it substitute comma with it.
I tried some SUBSTR and LISTAGG but couldn't get anything worth sharing.
Regards.

Here's one option; read comments within code.
SQL> with test (id, col) as
2 -- sample data
3 (select 1, 'abc,mno:EMP' from dual union all
4 select 2, 'xyz:EMP;tyu,opr:PROF' from dual union all
5 select 3, 'abc,mno:EMP;tyu,opr:PROF' from dual
6 ),
7 temp as
8 -- split sample data to rows
9 (select id,
10 column_value cv,
11 regexp_substr(col, '[^;]+', 1, column_value) val
12 from test cross join
13 table(cast(multiset(select level from dual
14 connect by level <= regexp_count(col, ';') + 1
15 ) as sys.odcinumberlist))
16 )
17 -- finally, replace comma with a string that follows a colon sign
18 select id,
19 listagg(replace(val, ',', substr(val, instr(val, ':')) ||';'), ';') within group (order by cv) new_val
20 from temp
21 group by id
22 order by id;
ID NEW_VAL
---------- ----------------------------------------
1 abc:EMP;mno:EMP
2 xyz:EMP;tyu:PROF;opr:PROF
3 abc:EMP;mno:EMP;tyu:PROF;opr:PROF
SQL>

Using the answer of littlefoot, if i were to use cross apply i wouldnt need to cast as multiset...
with test (id, col) as
-- sample data
(select 1, 'abc,mno:EMP' from dual union all
select 2, 'xyz:EMP;tyu,opr:PROF' from dual union all
select 3, 'abc,mno:EMP;tyu,opr:PROF' from dual
),
temp as
-- split sample data to rows
(select id,
column_value cv,
regexp_substr(col, '[^;]+', 1, column_value) val
from test
cross apply (select level as column_value
from dual
connect by level<= regexp_count(col, ';') + 1)
)
-- finally, replace comma with a string that follows a colon sign
select id,
listagg(replace(val, ',', substr(val, instr(val, ':')) ||';'), ';') within group (order by cv) new_val
from temp
group by id
order by id;

You do not need recursive anything, just basic regex: if the pattern is always something,something2:someCode (e.g. you have no colon before the comma), then it would be sufficient.
with test (id, col) as (
select 1, 'abc,mno:EMP' from dual union all
select 2, 'xyz:EMP;tyu,opr:PROF' from dual union all
select 3, 'abc,mno:EMP;tyu,opr:PROF' from dual union all
select 3, 'abc,mno:EMP;tyu,opr:PROF;something:QWE;something2:QWE' from dual
)
select
/*
Grab this groups:
1) Everything before the comma
2) Then everything before the colon
3) And then everything between the colon and a semicolon
Then place group 3 between 1 and 2
*/
trim(trailing ';' from regexp_replace(col || ';', '([^,]+),([^:]+):([^;]+)', '\1:\3;\2:\3')) as res
from test
| RES |
| :------------------------------------------------------------- |
| abc:EMP;mno:EMP |
| xyz:EMP;tyu:PROF;opr:PROF |
| abc:EMP;mno:EMP;tyu:PROF;opr:PROF |
| abc:EMP;mno:EMP;tyu:PROF;opr:PROF;something:QWE;something2:QWE |
db<>fiddle here

Related

How to search for the All the numbers after special character "|" using Oracle

Basically I have few records that in field that has special character usually like following:
id Content
1 1|1232
2 23|12323
3 33|233223
I would like to write a query that select the numbers on the right side of the pipe "|"
so the result should be as follows:
result for query
1 = 1232
2 = 12323
ext...
You can use simple string functions:
SELECT id,
SUBSTR( content, 1, INSTR( content, '|' ) - 1 ) AS before_pipe,
SUBSTR( content, INSTR( content, '|' ) + 1 ) AS after_pipe
FROM table_name
Or, using regular expressions:
SELECT id,
REGEXP_SUBSTR( content, '^\d+' ) AS before_pipe,
REGEXP_SUBSTR( content, '\d+$' ) AS after_pipe
FROM table_name
Which, for the sample data:
CREATE TABLE table_name ( id, content ) AS
SELECT 1, '1|1232' FROM DUAL UNION ALL
SELECT 2, '23|12323' FROM DUAL UNION ALL
SELECT 3, '33|233223' FROM DUAL;
Both output:
ID
BEFORE_PIPE
AFTER_PIPE
1
1
1232
2
23
12323
3
33
233223
db<>fiddle here
Based on sample data, see if any of these two options (substr + instr and regular expressions) help. Sample data in lines #1 - 5, query begins at line#6.
SQL> with test (id, content) as
2 (select 1, '1|1232' from dual union all
3 select 2, '23|12323' from dual union all
4 select 3, '33|233223' from dual
5 )
6 select id,
7 content,
8 --
9 substr(content, instr(content, '|') + 1) result_1,
10 regexp_substr(content, '\d+$') result_2
11 from test;
ID CONTENT RESULT_1 RESULT_2
---------- --------- --------------- ---------------
1 1|1232 1232 1232
2 23|12323 12323 12323
3 33|233223 233223 233223
SQL>

Monitoring data with delimiter to create a new rows

I want to create a rows from one row on table that contains a delimiter on some fields as mentioned below on screen shoot
I want a result A separalte rows for the rows that already contains a deleimiter ;
the inputs are data from table 1 and the output is data as mentionned below on table 2 using oracle sql :insert and select query
you can see below the output recommanded:
One method is a recursive CTE:
with cte(id, description, val, before, after, n, cnt) as (
select id, description, val, before, after, 1 as n, regexp_count(description, ';')
from t
union all
select id, description, val, before, after, n + 1, cnt
from cte
where n <= cnt
)
select id,
regexp_substr(description, '[^;]+', 1, n) as description,
regexp_substr(val, '[^;]+', 1, n) as val,
regexp_substr(before, '[^;]+', 1, n) as before,
regexp_substr(after, '[^;]+', 1, n) as after
from cte;
Here is a db<>fiddle.
Alternatively:
SQL> with test (id, description, val, after, before) as
2 -- you already have sample data and don't type this
3 (select 1, 'ARTIC1;ARTIC2;ART11', '15;2;3', '12;6;8', '13;7;12' from dual union all
4 select 2, 'ARTICLE3;ARTICLE4' , '3;5' , '10;23' , '12;25' from dual union all
5 select 3, 'ARTICLE 5' , '6' , '2' , '1.9' from dual
6 )
7 -- query that does the job begins here
8 select id,
9 regexp_substr(description, '[^;]+', 1, column_value) descr,
10 regexp_substr(val , '[^;]+', 1, column_value) val,
11 regexp_substr(after , '[^;]+', 1, column_value) after,
12 regexp_substr(before , '[^;]+', 1, column_value) before
13 from test cross join
14 table(cast(multiset(select level from dual
15 connect by level <= regexp_count(description, ';') + 1
16 ) as sys.odcinumberlist))
17 order by id, descr, val;
ID DESCR VAL AFTER BEFORE
---------- ---------- ---------- ---------- ----------
1 ARTIC1 15 12 13
1 ARTIC2 2 6 7
1 ART11 3 8 12
2 ARTICLE3 3 10 12
2 ARTICLE4 5 23 25
3 ARTICLE 5 6 2 1.9
6 rows selected.
SQL>

Oracle REGEXP_SUBSTR returning NULL as value

I am trying to split data from a column into rows but I am facing this issue here.
When i run this query it splits the data fine but it is also returning NULL as an extra row too.
Here is the value I am trying to split ,162662163,90133140,163268955,169223426,169222899,
WITH CTE AS(
SELECT
RTRIM(LTRIM(PG2.MULTILIST11, ','), ',') ACCESS_BY_GROUPS
FROM AGILE.ITEM I
INNER JOIN AGILE.PAGE_TWO PG2 ON PG2.ID = I.ID
WHERE
ITEM_NUMBER IN --('313-000074',
('313-000090')
)
SELECT DISTINCT
REGEXP_SUBSTR(ACCESS_BY_GROUPS, '[^,]+', 1, column_value) ACCESS_BY_GROUPS
FROM CTE
CROSS JOIN TABLE(CAST(MULTISET(SELECT LEVEL FROM DUAL
CONNECT BY LEVEL <= REGEXP_COUNT(CTE.ACCESS_BY_GROUPS, ',') + 10
) AS sys.odcinumberlist))
I don't want to get that null value. I cannot apply that check out of the query because it will affect other column values too so I want it to be handled somewhere in the function. I hope someone can help.
It is because of leading and trailing commas.
One option is to begin from position 2 (see line #3) and limit number of values (line #5)
SQL> with test (col) as
2 (select ',162662163,90133140,163268955,169223426,169222899,' from dual)
3 select regexp_substr(col, '[^,]+', 2, level) res
4 from test
5 connect by level <= regexp_count(col, ',') - 1
6 /
RES
-------------------------------------------------
162662163
90133140
163268955
169223426
169222899
SQL>
Another is to remove leading and trailing comma first, and then split the rest into rows:
SQL> with test (col) as
2 (select ',162662163,90133140,163268955,169223426,169222899,' from dual),
3 temp as
4 -- remove leading and trailing comma first
5 (select ltrim(rtrim(col, ','), ',') col
6 from test
7 )
8 select regexp_substr(col, '[^,]+', 1, level) res
9 from temp
10 connect by level <= regexp_count(col, ',') + 1;
RES
------------------------------------------------
162662163
90133140
163268955
169223426
169222899
SQL>
[EDIT - for more than a single row]
If there are more rows involved, code has to be changed. Note that there must be some kind of a unique identifier for every row (ID in my example).
SQL> with test (id, col) as
2 (select 1, ',162662163,90133140,163268955,169223426,169222899,' from dual union all
3 select 2, ',1452761,1452762,' from dual
4 )
5 select id,
6 regexp_substr(col, '[^,]+', 2, column_value) res
7 from test cross join table(cast(multiset(select level from dual
8 connect by level <= regexp_count(col, ',') - 1
9 ) as sys.odcinumberlist))
10 order by id, column_value
11 /
ID RES
---------- -------------------------------------------------
1 162662163
1 90133140
1 163268955
1 169223426
1 169222899
2 1452761
2 1452762
7 rows selected.
SQL>

Oracle Regex Connect By

I am trying to produce multiple rows after performing a regex on a column splitting all values in square brackets. I'm only able to return a single value though, currently.
The field I am performing the regex has this value:
[1265]*[1263]
I am trying to get 1265 and 1263 in my result set as separate rows.
SELECT REGEXP_SUBSTR(column,'\[(.*?)\]',1,LEVEL) AS "col1"
FROM table
CONNECT BY REGEXP_SUBSTR(column,'\[(.*?)\]',1,LEVEL) IS NOT NULL;
Instead I just get this in the result set.
[1263]
with test (rn, col) as
(
select 'a', '[123]*[abc] []' from dual union all
select 'b', '[45][def] ' from dual union all
select 'c', '[678],.*' from dual
),
coll (rn, col) as
(
select rn,regexp_replace(col, '(\[.*?\])|.', '\1') from test
),
cte (rn, cnt, col, i) as
(
select rn, 1, col, regexp_substr(col, '(\[(.*?)\])', 1, 1, null, 2)
from coll
union all
select rn, cnt+1, col, regexp_substr(col, '(\[(.*?)\])', 1, cnt+1, null, 2)
from cte
where cnt+1 <= regexp_count(col, '\[.*?\]')
)
select * from cte
order by 1,2;
This regex counts elements by looking for closing brackets and returns the digits inside the brackets, allowing for NULLs. Separators are ignored since the data elements you want are surrounded by square brackets we can focus on those.
SQL> with test(rownbr, col) as (
select 1, '[1265]**[1263]' from dual union
select 2, '[123]' from dual union
select 3, '[111][222]*[333]' from dual union
select 4, '[411]*[][433]' from dual
)
select distinct rownbr, level as element,
regexp_substr(col, '\[([0-9]*)\]', 1, level, null, 1) value
from test
connect by level <= regexp_count(col, ']')
order by rownbr, element;
ROWNBR ELEMENT VALUE
---------- ---------- -----
1 1 1265
1 2 1263
2 1 123
3 1 111
3 2 222
3 3 333
4 1 411
4 2
4 3 433
9 rows selected.
SQL>

Oracle SQL : Regexp_substr

I have below sample values in a column
Abc-123-xyz
Def-456-uvw
Ghi-879-rst-123
Jkl-abc
Expected output is the third element split by '-', in case there is no third element, the last element will be retrieve.
See expected output below:
Xyz
Uvw
Rst
Abc
Thanks ahead for the help.
SELECT initcap(nvl(regexp_substr(word, '[^-]+', 1,3),regexp_substr(word, '[^-]+', 1,2))) FROM your_table;
Another approach:
SQL> with t1(col) as(
2 select 'Abc-123-xyz' from dual union all
3 select 'Def-456-uvw' from dual union all
4 select 'Ghi-879-rst-123' from dual union all
5 select 'Jkl-Abc' from dual
6 )
7 select regexp_substr( col
8 , '[^-]+'
9 , 1
10 , case
11 when regexp_count(col, '[^-]+') >= 3
12 then 3
13 else regexp_count(col, '[^-]+')
14 end
15 ) as res
16 from t1
17 ;
Result:
RES
---------------
xyz
uvw
rst
Abc
regexp_substr(column, '(.*?-){0,2}([^-]+)', 1, 1, '', 2)
You can also do it without RegEx:
with t1 as(
select 'Abc-123-xyz' as MyText from dual union all
select 'Def-456-uvw' from dual union all
select 'Ghi-879-rst-123' from dual union all
select 'Jkl-Abc' from dual
)
SELECT
SUBSTR(t1.mytext, LENGTH(t1.mytext) - INSTR(REVERSE(t1.mytext), '-') + 2)
FROM t1
;