how to split a string which is having comma and colon - sql

I have a following query like this
SELECT REGEXP_SUBSTR('SARAH;10,JOE;1D,KANE;1A,SDF:1a', '[^,;]+', 1, level)
FROM dual
CONNECT BY REGEXP_SUBSTR('SARAH;10,JOE;1D,KANE;1A,SDF:1a',
'[^,;]+',
1,
level) IS NOT NULL;
I am trying to get o/p as SARAH,JOE,KANE,SDF

If there's only one row of data, then you can use
WITH t(str) AS
(
SELECT 'SARAH;10,JOE;1D,KANE;1A,SDF:1a' FROM dual
), t2 AS
(
SELECT level AS lvl, REGEXP_SUBSTR(str, '[^,;:]+', 1, level) AS str
FROM t
CONNECT BY REGEXP_SUBSTR(str,
'[^,;]+',
1,
level) IS NOT NULL
)
SELECT LISTAGG(str,',') WITHIN GROUP (ORDER BY lvl) AS result
FROM t2
WHERE NOT REGEXP_LIKE(str,'^(\d)')
in order to filter the extracted substrings which don't start with an integer through use of REGEXP_LIKE() like above

Don't split the string and re-aggregate. Just replace the string from each ; or : until the next , or then end-of-the-string:
SELECT REGEXP_REPLACE(
'SARAH;10,JOE;1D,KANE;1A,SDF:1a',
'[;:].*?(,|$)',
'\1'
) AS replaced_value
FROM DUAL;
Which outputs:
REPLACED_VALUE
SARAH,JOE,KANE,SDF
db<>fiddle here
Update
If your delimiter can be any one of the ;:, characters until the next ;:, character or the end-of-the-string then:
SELECT value,
RTRIM(REGEXP_REPLACE(value, '[;:,].*?([;:,]|$)', ','), ',')
AS replaced_value
FROM table_name;
Which, for the sample data:
CREATE TABLE table_name (value) AS
SELECT 'SARAH;10,JOE;1D,KANE;1A,SDF:1a' FROM DUAL UNION ALL
SELECT 'SARAH,10;JOE,1D;KANE,1A;SDF:1a' FROM DUAL;
Outputs:
VALUE
REPLACED_VALUE
SARAH;10,JOE;1D,KANE;1A,SDF:1a
SARAH,JOE,KANE,SDF
SARAH,10;JOE,1D;KANE,1A;SDF:1a
SARAH,JOE,KANE,SDF
db<>fiddle here

Related

How to reformat data stored in a record and output it in oracle

Let's go about this with an example
Let's say my table column holds the following data
Col_ID Col_Name
------ ---------------------------------------------------
102 LOCATION_ID IN (7351,7550,76202,7350)
121 265,76700,76701,72701,74210)
111 ,76200,76201,76202,76203,76204,76205,76206,76207,7
The above data is stored in that manner in the tables already which cannot be changed. The output that I desire is as follows:-
Col_ID Col_Name
------ --------
102 7531
102 7550
102 76202
102 7350
121 265
121 76700
And So on.....
There are multiple ways of extracting data from a delimited string.
One of which is to use a recursive sub-query factoring clause:
WITH data ( col_id, col_name, lvl, max_lvl, value ) AS (
SELECT col_id,
col_name,
1, -- First level
REGEXP_COUNT( col_name, '\d+' ), -- Count the number of numbers
TO_NUMBER( REGEXP_SUBSTR( col_name, '\d+', 1, 1 ) ) -- Extract the first number
FROM your_table
WHERE 1 <= REGEXP_COUNT( col_name, '\d+' ) -- Check there is a number
UNION ALL -- Iterate over the previous rows
SELECT col_id,
col_name,
lvl + 1, -- Increase the level by one
max_lvl,
TO_NUMBER( REGEXP_SUBSTR( col_name, '\d+', 1, lvl + 1 ) )
-- Extract the (lvl+1)th number from the string.
FROM data
WHERE lvl < max_lvl -- Continue until all the numbers have been parsed
)
SELECT col_id,
value
FROM data;
The parenthesis is removed with some pattern, so try as below and make necessary modifications.
WITH outer_table
AS (SELECT col_id, col_name
FROM (SELECT col_id,
REGEXP_SUBSTR (col_name,
'[^)]+',
1,
1)
col_name
FROM t5
WHERE col_name LIKE '%)%'
UNION ALL
SELECT col_id,
REGEXP_SUBSTR (col_name,
'[^(]+',
1,
2)
col_name
FROM t5
WHERE col_name LIKE '%(%'
UNION ALL
SELECT col_id, (col_name) col_name
FROM t5
WHERE col_name NOT LIKE '%(%' AND col_name NOT LIKE '%)%'))
select col_id, regexp_substr(col_name,'[^,]+',1,column_value) col_name
from outer_table,
table(
cast(
multiset(
select level
from dual
connect by level <= length(regexp_replace(col_name,'[^,]')) + 1
)
as sys.OdciNumberList
)
)
ORDER BY col_id,col_name
Demo
WITH T AS
(SELECT '102' AS COL_ID, 'LOCATION_ID IN (7351,7550,76202,7350)' AS COL_NAME
FROM DUAL
UNION ALL
SELECT '121', '265,76700,76701,72701,74210)'
FROM DUAL
UNION ALL
SELECT '111', ',76200,76201,76202,76203,76204,76205,76206,76207,7'
FROM DUAL)
SELECT COL_ID, COL_NAME
FROM (SELECT T.COL_ID,
REGEXP_SUBSTR(REGEXP_SUBSTR(T.COL_NAME, '[^,]+', 1,
T1.COLUMN_VALUE), '[0-9]+', 1, 1) AS COL_NAME
FROM T,
TABLE(CAST(MULTISET
(SELECT LEVEL
FROM DUAL
CONNECT BY LEVEL <=
REGEXP_COUNT(T.COL_NAME, ',') + 1) AS
SYS.ODCINUMBERLIST)) T1)
WHERE COL_NAME IS NOT NULL

How to sort version numbers (like 5.3.60.8)

I have a Strings like:
5.3.60.8
6.0.5.94
3.3.4.1
How to sort these values in sorting order in Oracle SQL?
I want the order to be like this:
6.0.5.94
5.3.60.8
3.3.4.1
with
inputs ( str ) as (
select '6.0.5.94' from dual union all
select '5.3.60.8' from dual union all
select '3.3.4.1' from dual
)
select str from inputs
order by to_number(regexp_substr(str, '\d+', 1, 1)),
to_number(regexp_substr(str, '\d+', 1, 2)),
to_number(regexp_substr(str, '\d+', 1, 3)),
to_number(regexp_substr(str, '\d+', 1, 4))
;
STR
--------
3.3.4.1
5.3.60.8
6.0.5.94
You could pad numbers with zeroes on the left in the order by clause:
select version
from versions
order by regexp_replace(
regexp_replace(version, '(\d+)', lpad('\1', 11, '0')),
'\d+(\d{10})',
'\1'
) desc
This works for more number parts as well, up to about 200 of them.
If you expect to have numbers with more than 10 digits, increase the number passed as second argument to the lpad function, and also the braced number in the second regular expression. The first should be one more (because \1 is two characters but could represent only one digit).
Highest version
To get the highest version only, you can add the row number to the query above with the special Oracle rownum keyword. Then wrap all that in an another select with a condition on that row number:
select version
from (
select version, rownum as row_num
from versions
order by regexp_replace(
regexp_replace(version, '(\d+)', lpad('\1', 11, '0')),
'\d+(\d{10})',
'\1'
) desc)
where row_num <= 1;
See this Q&A for several alternatives, also depending on your Oracle version.
I will show here the answer from AskTom, which can be used with different version size :
WITH inputs
AS (SELECT 1 as id, '6.0.5.94' as col FROM DUAL
UNION ALL
SELECT 2,'5.3.30.8' FROM DUAL
UNION ALL
SELECT 3,'5.3.4.8' FROM DUAL
UNION ALL
SELECT 4,'3' FROM DUAL
UNION ALL
SELECT 5,'3.3.40' FROM DUAL
UNION ALL
SELECT 6,'3.3.4.1.5' FROM DUAL
UNION ALL
SELECT 7,'3.3.4.1' FROM DUAL)
SELECT col, MAX (SYS_CONNECT_BY_PATH (v, '.')) p
FROM (SELECT t.col, TO_NUMBER (SUBSTR (x.COLUMN_VALUE, 1, 5)) r, SUBSTR (x.COLUMN_VALUE, 6) v, id rid
FROM inputs t,
TABLE (
CAST (
MULTISET (
SELECT TO_CHAR (LEVEL, 'fm00000')
|| TO_CHAR (TO_NUMBER (SUBSTR ('.' || col || '.', INSTR ('.' || col || '.', '.', 1, ROWNUM) + 1, INSTR ('.' || col || '.', '.', 1, ROWNUM + 1) - INSTR ('.' || col || '.', '.', 1, ROWNUM) - 1)), 'fm0000000000')
FROM DUAL
CONNECT BY LEVEL <= LENGTH (col) - LENGTH (REPLACE (col, '.', '')) + 1) AS SYS.odciVarchar2List)) x)
START WITH r = 1
CONNECT BY PRIOR rid = rid AND PRIOR r + 1 = r
GROUP BY col
ORDER BY p

Escaping special characters for JSON output

I have a column that contains data that I want to escape in order to use it as JSON output, to be more precise am trying to escape the same characters listed here but using Oracle 11g: Special Characters and JSON Escaping Rules
I think it can be solved using REGEXP_REPLACE:
SELECT REGEXP_REPLACE(my_column, '("|\\|/)|(' || CHR(9) || ')', '\\\1') FROM my_table;
But I am lost about replacing the other characters (tab, new line, backspace, etc), in the previous example I know that \1 will match and replace the first group but I am not sure how to capture the tab in the second group and then replace it with \t. Somebody could give me a hint about how to do the replacement?
I know I can do this:
SELECT REGEXP_REPLACE( REGEXP_REPLACE(my_column, '("|\\|/)', '\\\1'), '(' || CHR(9) || ')', '\t')
FROM my_table;
But I would have to nest like 5 calls to REGEXP_REPLACE, and I suspect I should be able to do it in just one or two calls.
I am aware about other packages or libraries for JSON but I think this case is simple enough that it can be solved with the functions that Oracle offers out-of-the-box.
Thank you.
Here's a start. Replacing all the regular characters is easy enough, it's the control characters that will be tricky. This method uses a group consisting of a character class that contains the characters you want to add the backslash in front of. Note that characters inside of the class do not need to be escaped. The argument to REGEXP_REPLACE of 1 means start at the first position and the 0 means to replace all occurrences found in the source string.
SELECT REGEXP_REPLACE('t/h"is"'||chr(9)||'is a|te\st', '([/\|"])', '\\\1', 1, 0) FROM dual;
Replacing the TAB and a carriage return is easy enough by wrapping the above in REPLACE calls, but it stinks to have to do this for each control character. Thus, I'm afraid my answer isn't really a full answer for you, it only helps you with the regular characters a bit:
SQL> SELECT REPLACE(REPLACE(REGEXP_REPLACE('t/h"is"'||chr(9)||'is
2 a|te\st', '([/\|"])', '\\\1', 1, 0), chr(9), '\t'), chr(10), '\n') fixe
3 FROM dual;
FIXED
-------------------------
t\/h\"is\"\tis\na\|te\\st
SQL>
EDIT: Here's a solution! I don't claim to understand it fully, but basically it creates a translation table that joins to your string (in the inp_str table). The connect by, level traverses the length of the string and replaces characters where there is a match in the translation table. I modified a solution found here: http://database.developer-works.com/article/14901746/Replace+%28translate%29+one+char+to+many that really doesn't have a great explanation. Hopefully someone here will chime in and explain this fully.
SQL> with trans_tbl(ch_frm, str_to) as (
select '"', '\"' from dual union
select '/', '\/' from dual union
select '\', '\\' from dual union
select chr(8), '\b' from dual union -- BS
select chr(12), '\f' from dual union -- FF
select chr(10), '\n' from dual union -- NL
select chr(13), '\r' from dual union -- CR
select chr(9), '\t' from dual -- HT
),
inp_str as (
select 'No' || chr(12) || 'w is ' || chr(9) || 'the "time" for /all go\od men to '||
chr(8)||'com' || chr(10) || 'e to the aid of their ' || chr(13) || 'country' txt from dual
)
select max(replace(sys_connect_by_path(ch,'`'),'`')) as txt
from (
select lvl
,decode(str_to,null,substr(txt, lvl, 1),str_to) as ch
from inp_str cross join (select level lvl from inp_str connect by level <= length(txt))
left outer join trans_tbl on (ch_frm = substr(txt, lvl, 1))
)
connect by lvl = prior lvl+1
start with lvl = 1;
TXT
------------------------------------------------------------------------------------------
No\fw is \tthe \"time\" for \/all go\\od men to \bcom\ne to the aid of their \rcountry
SQL>
EDIT 8/10/2016 - Make it a function for encapsulation and reusability so you could use it for multiple columns at once:
create or replace function esc_json(string_in varchar2)
return varchar2
is
s_converted varchar2(4000);
BEGIN
with trans_tbl(ch_frm, str_to) as (
select '"', '\"' from dual union
select '/', '\/' from dual union
select '\', '\\' from dual union
select chr(8), '\b' from dual union -- BS
select chr(12), '\f' from dual union -- FF
select chr(10), '\n' from dual union -- NL
select chr(13), '\r' from dual union -- CR
select chr(9), '\t' from dual -- HT
),
inp_str(txt) as (
select string_in from dual
)
select max(replace(sys_connect_by_path(ch,'`'),'`')) as c_text
into s_converted
from (
select lvl
,decode(str_to,null,substr(txt, lvl, 1),str_to) as ch
from inp_str cross join (select level lvl from inp_str connect by level <= length(txt))
left outer join trans_tbl on (ch_frm = substr(txt, lvl, 1))
)
connect by lvl = prior lvl+1
start with lvl = 1;
return s_converted;
end esc_json;
Example to call for multiple columns at once:
select esc_json(column_1), esc_json(column_2)
from your_table;
Inspired by the answer above, I created this simpler "one-liner" function:
create or replace function json_esc (
str IN varchar2
) return varchar2
begin
return REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(str, chr(8), '\b'), chr(9), '\t'), chr(10), '\n'), chr(12), '\f'), chr(13), '\r');
end;
Please note, both this and #Gary_W's answer above are not escaping all control characters as the json.org seems to indicate.
in sql server you can use STRING_ESCAPE() function like below:
SELECT
STRING_ESCAPE('['' This is a special / "message" /'']', 'json') AS
escapedJson;

How to trim/substr from varchar when decimal place found in oracle?

How to trim after decimal(.) from varchar2 in oracle?
Example 1: '2999.89' should be return '2999'
Example 1: 'V59.00' should be return 'V59'
You could use SUBSTR and INSTR.
For example,
SQL> WITH DATA(str) AS(
2 SELECT '2999.89' FROM dual UNION ALL
3 SELECT 'V59.00' FROM dual
4 )
5 SELECT str, SUBSTR(str, 1, instr(str, '.', 1, 1)-1) new_str FROM DATA;
STR NEW_STR
------- -------
2999.89 2999
V59.00 V59
SQL>
You can use instr to find the index of . and then substr to return the string up to that position:
SELECT SUBSTR (col, 1, INSTR(col, '.') - 1)
FROM mytable
WITH DATA(str) AS(
SELECT '2999.89' FROM dual UNION ALL
SELECT 'V59.00' FROM dual
)
SELECT str, REGEXP_SUBSTR(str,'^([^.]*)',1) new_str FROM DATA;

Distinct Comma separated values in oracle

I have a string as '1,1,2,3,4,4,5,6,6,7' stored in a column.
I need distinct comma separated value as output using sql query.
e.g. For given string output should be '1,2,3,4,5,6,7'. No duplicacy persists in output.
without regexp:
WITH t AS
( SELECT '1,2,3,3,3,4,5,6,7,7,7,7' AS num FROM dual
)
SELECT DISTINCT
SUBSTR (
num
, instr(num, ',', 1, level) + 1
, instr(num, ',', 1, level + 1) - instr(num, ',', 1, level) - 1)
AS numbers
FROM (select ','||num||',' num from t)
CONNECT BY level <= length(num) - length(replace(num,',')) -1
with regexp:
SELECT DISTINCT REGEXP_SUBSTR( '1,1,2,3,4,4,5,6,6,7' , '[^,]+', 1, lvl)
FROM DUAL,
(SELECT LEVEL lvl
FROM DUAL
CONNECT BY LEVEL <= LENGTH( '1,1,2,3,4,4,5,6,6,7' ) - LENGTH(REPLACE( '1,1,2,3,4,4,5,6,6,7' , ','))+1)
WHERE lvl <= LENGTH( '1,1,2,3,4,4,5,6,6,7' ) - LENGTH(REPLACE( '1,1,2,3,4,4,5,6,6,7' , ',')) + 1
Try
select
regexp_replace('1,1,2,3,4,4,5,6,6,7', '([^,]+),\1', '\1')
from
dual;
However, this wont work if your input string contains a figure more than twice. If this bothers you, you might want to try
select
regexp_replace('1,1,2,3,4,4,4,5,6,6,6,6,6,6,7', '([^,]+)(,\1)+', '\1')
from dual;
We can do this using regex_substr and connect by. Please try this.
select distinct num from
(SELECT REGEXP_SUBSTR('1,1,2,3,4,4,5,6,6,7','[^,]+',1,level) as num
FROM DUAL
CONNECT BY LEVEL<= LENGTH(REGEXP_REPLACE('1,1,2,3,4,4,5,6,6,7','[^,]','')));
Without regex:
After some clarification in the question
with t as (SELECT distinct substr(replace('1,1,2,3,4,4,5,6,6,7',','),level,1)||',' as num
FROM DUAL
CONNECT BY LEVEL<= LENGTH( '1,1,2,3,4,4,5,6,6,7' ) - LENGTH(REPLACE( '1,1,2,3,4,4,5,6,6,7' , ','))+1)
select listagg(num) within group (order by num) from t;
According my point of view:
select wm_concat(distinct substr(replace('1,1,2,3,4,4,5,6,6,7',',',''),level,1)) as out
from dual connect by level <= length('1,1,2,3,4,4,5,6,6,7');
SELECT
listagg(ra,',') WITHIN GROUP (ORDER BY ra)
FROM
(
SELECT
DISTINCT (REGEXP_SUBSTR('02,02,02,02,02,03,04,03', '[^,]+', 1, LEVEL) )ra
FROM DUAL
CONNECT BY REGEXP_SUBSTR('02,02,02,02,02,03,04,03', '[^,]+', 1, LEVEL) IS
NOT NULL
);