Distinct Comma separated values in oracle - sql

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
);

Related

how to split a string which is having comma and colon

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

Filling in a table in the range between "L00000000" and "L00000010" in an oracle

I need help. I was once helped with a code to populate a table in the range between the first and last values. This code works fine, but there is one problem I can't figure out.
When I use as finite numbers like P1_FIRST = L4819222 and P1_LAST = L4819225, everything works fine. But when I use extreme chills as L00000000 and L00000010, I get: L1, L2, L3 ... but I wanted to see: `L00000000, L00000001, L00000002, etc.
Can you help me in this?
INSERT INTO test2 (val)
SELECT SUBSTR (:P1_FIRST, 1, 1)
|| TO_CHAR (
( TO_NUMBER (REGEXP_SUBSTR (:P1_FIRST, '\d+$'))
+ LEVEL
- 1))
AS val
FROM dual
CONNECT BY LEVEL <=
TO_NUMBER (
REGEXP_SUBSTR (:P1_LAST, '\d+$'))
- TO_NUMBER (
REGEXP_SUBSTR
SQL> select 'L' || lpad(level - 1, 8, '0') val
2 from dual
3 connect by level <= 11;
VAL
---------
L00000000
L00000001
L00000002
L00000003
L00000004
L00000005
L00000006
L00000007
L00000008
L00000009
L00000010
11 rows selected.
SQL>
As of INSERT and Apex items:
SQL> create table test (val varchar2(20));
Table created.
SQL> insert into test (val)
2 with subs as
3 (select to_number(regexp_substr('&P1_LAST' , '\d+$')) clast,
4 to_number(regexp_substr('&P1_FIRST', '\d+$')) cfirst
5 from dual
6 )
7 select 'L' || lpad(cfirst + level - 1, 8, '0') val
8 from subs
9 connect by level <= clast - cfirst + 1;
Enter value for p1_last: L4819225
Enter value for p1_first: L4819222
4 rows created.
SQL> /
Enter value for p1_last: L0000010
Enter value for p1_first: L0000000
11 rows created.
Result:
SQL> select * From test order by val;
VAL
--------------------
L00000000
L00000001
L00000002
L00000003
L00000004
L00000005
L00000006
L00000007
L00000008
L00000009
L00000010
L04819222
L04819223
L04819224
L04819225
15 rows selected.
SQL>
Query you'll use in Apex is
insert into test (val)
with subs as
(select to_number(regexp_substr(:P1_LAST , '\d+$')) clast,
to_number(regexp_substr(:P1_FIRST, '\d+$')) cfirst
from dual
)
select 'L' || lpad(cfirst + level - 1, 8, '0') val
from subs
connect by level <= clast - cfirst + 1;
You need using LPAD. Try:
SELECT SUBSTR (:P1_FIRST, 1, 1)
||
LPAD(TO_CHAR ( TO_NUMBER (REGEXP_SUBSTR (:P1_FIRST, '\d+$')) + level), 8,'0') AS val
FROM dual
connect by level < 10:
in your case:
SELECT SUBSTR ('L00000000', 1, 1) || lpad(TO_CHAR ( TO_NUMBER (REGEXP_SUBSTR ('L00000000', '\d+$' )) + LEVEL - 1), 8, '0') AS val
FROM dual
CONNECT BY LEVEL <= TO_NUMBER ( REGEXP_SUBSTR ('L00000010', '\d+$')) - TO_NUMBER ( REGEXP_SUBSTR ('L00000000', '\d+$')) + 1

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

Oracle - string combinatorial permutation

I think I have a complex requirement.
It's a combinatorial permutation using Oracle 10.2, I'was able to solve it using cartesian joins, but I think that it need some improvements to made it simplest and more flexible.
Main behaviour.
input string: 'one two'
output:
'one'
'two'
'one two'
'two one'
For my solution I've restricted the number of strings to 5 (note that the output is a number near the factorial)
SQL:
with My_Input_String as (select 1 as str_id, 'alpha beta omega gama' as str from dual )
--------logic-------
, String_Parse as (
SELECT REGEXP_SUBSTR(str, '[^ ]+', 1, ROWNUM) str
FROM My_Input_String
where rownum < 6 -- string limitation --
CONNECT BY level <= LENGTH(REGEXP_REPLACE(str, '([^ ])+|.', '\1') )
)
--------CRAP select need refactoring-------
select str from String_Parse
union
select REGEXP_REPLACE(trim(s1.str||' '||s2.str||' '||s3.str||' '||s4.str||' '||s5.str), '( ){2,}', ' ') as str
from
(select str from String_Parse union select ' ' from dual) s1,
(select str from String_Parse union select ' ' from dual) s2,
(select str from String_Parse union select ' ' from dual) s3,
(select str from String_Parse union select ' ' from dual) s4,
(select str from String_Parse union select ' ' from dual) s5
where
--
s1.str <> s2.str and s1.str <> s3.str and s1.str <> s4.str and s1.str <> s5.str
--
and s2.str <> s3.str and s2.str <> s4.str and s2.str <> s5.str
--
and s3.str <> s4.str and s3.str <> s5.str
--
and s4.str <> s5.str
Edit: Got the generic one. Really simple in the end (but took me a while to get there)
WITH words AS
( SELECT REGEXP_SUBSTR( '&txt', '\S+', 1, LEVEL ) AS word
, LEVEL AS num
FROM DUAL
CONNECT BY LEVEL <= LENGTH( REGEXP_REPLACE( '&txt', '\S+\s*', 'X' ) )
)
SELECT SYS_CONNECT_BY_PATH( W.word, ' ' )
FROM words W
CONNECT BY NOCYCLE PRIOR W.num != W.num
Edit2: Removed redundant maxnum stuff. Left over from previous attempts