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
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
I'm looking for a way to achieve this in a SELECT statement.
FROM
Column1 Column2 Column3
A,B,C 1,2,3 x,y,z
TO
Result
A|1|x,B|2|y,C|3|z
The delimiters don't matter. I'm just trying to to get all the data in one single column. Ideally I am looking to do this in DB2. But I'd like to know if there's an easier way to get this done in Oracle.
Thanks
You can do it like this using INSTR and SUBSTR:
select
substr(column1,1,instr(column1,',',1)-1) || '|' ||
substr(column2,1,instr(column2,',',1)-1) || '|' ||
substr(column3,1,instr(column3,',',1)-1) || '|' ||
',' ||
substr(column1 ,instr(column1 ,',',1,1)+1,instr(column1 ,',',1,2) - instr(column1 ,',',1)-1) || '|' ||
substr(column2 ,instr(column2 ,',',1,1)+1,instr(column2 ,',',1,2) - instr(column2 ,',',1)-1) || '|' ||
substr(column3 ,instr(column3 ,',',1,1)+1,instr(column3 ,',',1,2) - instr(column3 ,',',1)-1) || '|' ||
',' ||
substr(column1 ,instr(column1 ,',',1,2)+1) || '|' ||
substr(column2 ,instr(column2 ,',',1,2)+1) || '|' ||
substr(column3 ,instr(column3 ,',',1,2)+1)
from yourtable
i tried some thing. just look into link
first i created a table called t_ask_test and inserted the data based on the above question. Achieved the result by using the string functions
sample table
create table t_ask_test(column1 varchar(10), column2 varchar(10),column3 varchar(10));
inserted a row
insert into T_ASK_TEST values ('A,B,C','1,2,3','x,y,z');
the following query will be in dynamic way
select substr(column1,1,instr(column1,',',1,1)-1)||'|'||substr(column2,1,instr(column1,',',1,1)-1)||'|'||substr(column3,1,instr(column1,',',1,1)-1) ||','||
substr(column1,instr(column1,',',1,1)+1,instr(column1,',',1,2)-instr(column1,',',1,1)-1)||'|'||substr(column2,instr(column2,',',1,1)+1,instr(column2,',',1,2)-instr(column2,',',1,1)-1)||'|'||substr(column3,instr(column3,',',1,1)+1,instr(column3,',',1,2)-instr(column3,',',1,1)-1) ||','||
substr(column1,instr(column1,',',1,2)+1,length(column1)-instr(column1,',',1,2))||'|'||substr(column2,instr(column2,',',1,2)+1,length(column2)-instr(column2,',',1,2))||'|'||substr(column3,instr(column3,',',1,2)+1,length(column3)-instr(column3,',',1,2)) as test from t_ask_test;
output will be as follows
TEST
---------------
A|1|x,B|2|y,C|3|z
If you have a dynamic number of entries for each row then:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE TEST ( Column1, Column2, Column3 ) AS
SELECT 'A,B,C', '1,2,3', 'x,y,z' FROM DUAL
UNION ALL SELECT 'D,E', '4,5', 'v,w' FROM DUAL;
Query 1:
WITH ids AS (
SELECT t.*, ROWNUM AS id
FROM TEST t
)
SELECT LISTAGG(
REGEXP_SUBSTR( i.Column1, '[^,]+', 1, n.COLUMN_VALUE )
|| '|' || REGEXP_SUBSTR( i.Column2, '[^,]+', 1, n.COLUMN_VALUE )
|| '|' || REGEXP_SUBSTR( i.Column3, '[^,]+', 1, n.COLUMN_VALUE )
, ','
) WITHIN GROUP ( ORDER BY n.COLUMN_VALUE ) AS value
FROM ids i,
TABLE(
CAST(
MULTISET(
SELECT LEVEL
FROM DUAL
CONNECT BY LEVEL <= GREATEST(
REGEXP_COUNT( i.COLUMN1, '[^,]+' ),
REGEXP_COUNT( i.COLUMN2, '[^,]+' ),
REGEXP_COUNT( i.COLUMN3, '[^,]+' )
)
)
AS SYS.ODCINUMBERLIST
)
) n
GROUP BY i.ID
Results:
| VALUE |
|-------------------|
| A|1|x,B|2|y,C|3|z |
| D|4|v,E|5|w |
You need to use:
SUBSTR
INSTR
|| concatenation operator
It would be easy if you break your output, and then understand how it works.
SQL> WITH t AS
2 ( SELECT 'A,B,C' Column1, '1,2,3' Column2, 'x,y,z' Column3 FROM dual
3 )
4 SELECT SUBSTR(column1, 1, instr(column1, ',', 1) -1)
5 ||'|'
6 || SUBSTR(column2, 1, instr(column2, ',', 1) -1)
7 ||'|'
8 || SUBSTR(column3, 1, instr(column1, ',', 1) -1)
9 ||','
10 || SUBSTR(column1, instr(column1, ',', 1, 2) +1 - instr(column1, ',', 1),
11 instr(column1, ',', 1) -1)
12 ||'|'
13 || SUBSTR(column2, instr(column2, ',', 1, 2) +1 - instr(column2, ',', 1),
14 instr(column2, ',', 1) -1)
15 ||'|'
16 || SUBSTR(column3, instr(column3, ',', 1, 2) +1 - instr(column3, ',', 1),
17 instr(column3, ',', 1) -1)
18 ||','
19 || SUBSTR(column1, instr(column1, ',', 1, 3) +1 - instr(column1, ',', 1),
20 instr(column1, ',', 2) -1)
21 as "new_column"
22 FROM t;
new_column
-------------
A|1|x,B|2|y,C
On a side note, you should avoid storing delimited values in a single column. Consider normalizing the data.
From Oracle 11g and above, you could create a VIRTUAL COLUMN using the above expression and use it instead of executing the SQL frequently.
Its very simple in oracle. just use the concatenation operatort ||.
In the below solution, I have used underscore as the delimiter
select Column1 ||'_'||Column2||'_'||Column3 from table_name;