Preserve order when converting a delimited string to a column - sql

I want to preserve the record order, which is provided as comma delimited string. The 5th item in by delimited string is a null. I need the 5th row to be null as well.
with test as
(select 'ABC,DEF,GHI,JKL,,MNO' str from dual
)
select rownum, regexp_substr (str, '[^,]+', 1, rownum) split
from test
connect by level <= length (regexp_replace (str, '[^,]+' )) + 1
The current result I'm getting puts this in the 6th position:
1 ABC
2 DEF
3 GHI
4 JKL
5 MNO
6

Order is preserved by your expression, but your regular expression doesn't match nulls correctly, so the 5th item disappears. The 6th row is a NULL because there are no more match after the 5th match.
You could do this instead:
SQL> with test as
2 (select 'ABC,DEF,GHI,JKL,,MNO' str from dual
3 )
4 SELECT rownum,
5 rtrim(regexp_substr(str || ',', '[^,]*,', 1, rownum), ',') split
6 FROM test
7 CONNECT BY LEVEL <= length(regexp_replace(str, '[^,]+')) + 1;
ROWNUM SPLIT
---------- ---------------------------------------------------------------
1 ABC
2 DEF
3 GHI
4 JKL
5
6 MNO
6 rows selected
Or this:
SQL> with test as
2 (select 'ABC,DEF,GHI,JKL,,MNO' str from dual
3 )
4 SELECT rownum,
5 regexp_substr(str, '([^,]*)(,|$)', 1, rownum, 'i', 1) split
6 FROM test
7 CONNECT BY LEVEL <= length(regexp_replace(str, '[^,]+')) + 1;
ROWNUM SPLIT
---------- ------------------------------------------------------------
1 ABC
2 DEF
3 GHI
4 JKL
5
6 MNO
6 rows selected

Try Something like this:
SELECT
STR,
REPLACE ( SUBSTR ( STR,
CASE LEVEL
WHEN 1
THEN
0
ELSE
INSTR ( STR,
'~',
1,
LEVEL
- 1 )
END
+ 1,
1 ),
'~' )
FROM
(SELECT 'A~~C~~E' AS STR FROM DUAL)
CONNECT BY
LEVEL <= LENGTH ( REGEXP_REPLACE ( STR,
'[^~]+' ) )
+ 1;

This one works..
SELECT
ROWNUM,
CAST ( REGEXP_SUBSTR ( STR,
'(.*?)(,|$)',
1,
LEVEL,
NULL,
1 ) AS CHAR ( 12 ) )
OUTPUT
FROM
(SELECT 'ABC,DEF,GHI,JKL,,MNO' AS STR FROM DUAL)
CONNECT BY
LEVEL <= REGEXP_COUNT ( STR,
',' )
+ 1;

Related

SUBSTR to ADD value in oracle

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

Splitting data field String value in Oracle SQl

I have a String Field value in Oracle SQL table. Is there any query so that I can split the string into new lines with certain number of equal characters in each line and the excess characters at the bottom?
eg- ABCDEFGHIJ
I want to have lines with equal number of characters 4 in each line as follows
ABCD
EFGH
IJ
The remainder of 2 letters should be at the bottom. Is it possible to achieve this using an Oracle sql query?
You can use a query like the one below using CONNECT BY and LEVEL based on the length of the string.
WITH d AS (SELECT 'ABCDEFGHIJ' AS str FROM DUAL)
SELECT SUBSTR (str, ((LEVEL - 1) * 4) + 1, 4) AS four_letters
FROM d
CONNECT BY LEVEL < (LENGTH (str) / 4) + 1;
If you have multiple rows, you can use OUTER APPLY with a hierarchical query:
SELECT s.split_value,
s.position
FROM table_name t
OUTER APPLY (
SELECT LEVEL AS position,
SUBSTR( t.value, 4 * LEVEL - 3, 4 ) AS split_value
FROM DUAL
CONNECT BY LEVEL <= CEIL( LENGTH( t.value ) / 4 )
) s
Which, for the sample data:
CREATE TABLE table_name ( value ) AS
SELECT 'ABCDEFGHIJ' FROM DUAL UNION ALL
SELECT '123456789012' FROM DUAL;
Outputs:
SPLIT_VALUE | POSITION
:---------- | -------:
ABCD | 1
EFGH | 2
IJ | 3
1234 | 1
5678 | 2
9012 | 3
db<>fiddle here
Update
if I want to have like 36 words characters in a line how can I modify your 1st answer?
SELECT s.split_value,
s.position
FROM table_name t
OUTER APPLY (
SELECT LEVEL AS position,
SUBSTR( t.value, 36 * ( LEVEL - 1 ) + 1, 36 ) AS split_value
FROM DUAL
CONNECT BY LEVEL <= CEIL( LENGTH( t.value ) / 36 )
) s
Which, for the sample data:
CREATE TABLE table_name ( value ) AS
SELECT 'ABCDEFGHIJ' FROM DUAL UNION ALL
SELECT '________10________20________30________40________50________60________70________80' FROM DUAL;
Outputs:
SPLIT_VALUE | POSITION
:----------------------------------- | -------:
ABCDEFGHIJ | 1
________10________20________30______ | 1
__40________50________60________70__ | 2
______80 | 3
db<>fiddle here

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>

how to split string with multiple special characters in oracle

I have a string with 2 special characters as below
String :'PAN~HLASD4564P|VOTER_ID~VDD3455355'
I want output in 2 columns as below :
ID_TYPE VALUE
------- ------
PAN HLASD4564P
VOTER_ID VDD3455355
enter image description here
You can use CONNECT BY and REGEXP_SUBSTR as follows:
SQL> WITH YOUR_DATA AS (
2 SELECT 'PAN~HLASD4564P|VOTER_ID~VDD3455355' AS STR
3 FROM DUAL
4 ) -- Your query starts from here
5 SELECT
6 REGEXP_SUBSTR(NEW_STR, '[^~]+', 1, 1) AS ID,
7 REGEXP_SUBSTR(NEW_STR, '[^~]+', 1, 2) AS VALUE
8 FROM
9 (
10 SELECT REGEXP_SUBSTR(STR, '[^|]+', 1, LEVEL) NEW_STR
11 FROM YOUR_DATA
12 CONNECT BY LEVEL <= 2
13 );
ID VALUE
---------- -------------
PAN HLASD4564P
VOTER_ID VDD3455355
SQL>

Get list of what special characters and how many times in oracle column

I'm searching for a way to get a list of special characters and how many times they appear in my column. I've tried using using regexp_count which works, but I'm not sure how to extend it to make it work for all special characters in one query.
For example for syntax = 'x=y*100' with the following query I get
select *
from (
select regexp_count(syntax, '\*') as charCnt, syntax
from tblTemp
)
where charCnt > 0
charCnt=1 and syntax='x=y*100'.
Which is correct but I want to be able to get back
specChar Cnt
\* 1
= 1
etc..
Oracle Setup:
CREATE TABLE table_name(
id INT,
value NVARCHAR2(200)
);
INSERT INTO table_name
SELECT 1, N'y=20x+3' FROM DUAL UNION ALL
SELECT 2, N'***^%$%$%*&*.&\?' FROM DUAL UNION ALL
SELECT 3, UNISTR('\00B5\00B6\00B5') FROM DUAL UNION ALL
SELECT 4, N'!"£$%^&*()!"£$%^&*()!"£$%^&*()!"£$%^&*()!"£$%^&*()'
|| N'!"£$%^&*()!"£$%^&*()!"£$%^&*()!"£$%^&*()!"£$%^&*()'
|| N'!"£$%^&*()!"£$%^&*()!"£$%^&*()!"£$%^&*()!"£$%^&*()'
|| N'!"£$%^&*()!"£$%^&*()!"£$%^&*()!"£$%^&*()!"£$%^&*()' FROM DUAL;
CREATE OR REPLACE TYPE CHAR_LIST IS TABLE OF CHAR(1 CHAR);
/
Query:
SELECT t.id,
--MAX( t.value ) AS value,
CAST( c.COLUMN_VALUE AS CHAR(1 CHAR) ) AS character,
COUNT(1) AS frequency
FROM table_name t,
TABLE(
CAST(
MULTISET(
SELECT SUBSTR( t.value, LEVEL, 1 )
FROM DUAL
WHERE REGEXP_LIKE( SUBSTR( t.value, LEVEL, 1 ), '[^a-zA-Z0-9]' )
CONNECT BY LEVEL <= LENGTH( t.value )
) AS CHAR_LIST
)
) c
GROUP BY t.id, c.COLUMN_VALUE
ORDER BY id, character;
Output:
ID CHARACTER FREQUENCY
---------- --------- ----------
1 + 1
1 = 1
2 $ 2
2 % 3
2 & 2
2 * 5
2 . 1
2 ? 1
2 \ 1
2 ^ 1
3 µ 2
3 ¶ 1
4 ! 20
4 " 20
4 $ 20
4 % 20
4 & 20
4 ( 20
4 ) 20
4 * 20
4 ^ 20
4 £ 20