How to arrange/sort string in Oracle SQL - 11g - sql

I have a string an string "ADBDkK" and I need to sort it as "ABDDKk", like Arrays.sort() in java. I know it can be done by using PL/SQL but I need of this in Oracle SQL statement.
Input:
ADBDkK
ZXYABC
Output:
ABDDKk
ABCXYZ

SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE test ( value ) AS
SELECT 'ADBDkK' FROM DUAL UNION ALL
SELECT 'ZXYABC' FROM DUAL;
Query 1:
WITH chars ( id, value, ch, lvl ) AS (
SELECT ROWNUM, value, SUBSTR( value, 1, 1 ), 1
FROM test
UNION ALL
SELECT id, value, SUBSTR( value, lvl+1, 1 ), lvl+1
FROM chars
WHERE lvl < LENGTH( value )
)
SELECT LISTAGG( ch ) WITHIN GROUP ( ORDER BY ch ) AS value
FROM chars
GROUP BY id
ORDER BY id
Results:
| VALUE |
|--------|
| ABDDKk |
| ABCXYZ |
Query 2:
SELECT LISTAGG( COLUMN_VALUE )
WITHIN GROUP ( ORDER BY COLUMN_VALUE ) AS value
FROM (
SELECT value,
ROWNUM AS id
FROM test
) t
CROSS JOIN
TABLE(
CAST(
MULTISET(
SELECT SUBSTR( t.value, LEVEL, 1 )
FROM DUAL
CONNECT BY LEVEL <= LENGTH( t.value )
)
AS SYS.ODCIVARCHAR2LIST
)
) c
GROUP BY t.id
ORDER BY t.id
Results:
| VALUE |
|--------|
| ABDDKk |
| ABCXYZ |

For a single string:
select listagg(regexp_substr('ADBDkK', '\w', 1 ,level),'')
within group (order by 1) from dual
connect by regexp_substr('ADBDkK', '\w', 1 ,level) is not null;
A slightly different way using a function:
CREATE OR REPLACE FUNCTION sort_string(my_string IN VARCHAR2)
RETURN VARCHAR2 IS
ret_string VARCHAR2(4000);
BEGIN
SELECT LISTAGG(regexp_substr(my_string, '\w', 1, level), '') WITHIN
GROUP(
ORDER BY 1)
INTO ret_string
FROM dual
CONNECT BY regexp_substr(my_string, '\w', 1, level) IS NOT NULL;
RETURN ret_string;
END;
Then from sqlplus:
SQL> select sort_string('ADBDkK') as RESULT from dual;
RESULT
------
ABDDKk
SQL> select sort_string('ZXYABC') as RESULT from dual;
RESULT
------
ABCXYZ

with t1 as (
select 'London Singapur' tmp from dual union all
select 'Singapur China' tmp from dual union all
select 'USA JAPAN ' tmp from dual union all
select 'JAPAN USA' tmp from dual union all
select 'Singapur London' tmp from dual
),
dst as ( select ROWNUM rwn,tmp,REGEXP_COUNT(tmp,'[^[:space:]]+') cnt_array from t1 ),
rc(id, cnt_id, tmp, CNT_ARRAY, SL) AS (
SELECT rwn id,1 cnt_id,tmp, CNT_ARRAY, regexp_substr(tmp,'[^[:space:]]+',1,1) sl FROM dst
UNION ALL
SELECT rc.id, rc.cnt_id+1 cnt_id, rc.tmp, rc.CNT_ARRAY, regexp_substr(rc.tmp,'[^[:space:]]+',1,rc.cnt_id+1) sl
FROM dst tr, rc
WHERE tr.rwn = rc.id and rc.cnt_id+1<=tr.CNT_ARRAY
),
srt as (select rc.*,row_number() over (partition by id order by id) nb,
listagg(sl,' ') WITHIN GROUP (ORDER BY sl) over(partition by id) fiel_srt
from rc)
select * from srt where nb=1

Related

modify column value if condition in oracle

I have below values in table, and need to set valid_values =6 when found >6
ID VALUE VALID_VALUES
---------- --------------- ---------------------------------------------
555 OFF OFF,1,2,3,4,5,6,7,8,9,10
So after change desired output would be as below,
SQL> /
FIS_ID VALUE VALID_VALUES
---------- --------------- ---------------------------------------------
417 OFF OFF,1,2,3,4,5,6,6,6,6,6
You do not need to split and aggregate; instead you can use a regular expression to find either 2-or-more-digit numbers (i.e. [1-9]\d+) or 1-digit values higher than 6 (i.e. [789]) and could include leading zeroes if these may appear in your data set (since you are storing numbers as text):
SELECT id,
value,
REGEXP_REPLACE(
valid_values,
'0*[1-9]\d+|0*[789]',
'6'
) AS valid_values
FROM table_name
Which, for the sample data:
CREATE TABLE table_name ( ID, VALUE, VALID_VALUES ) AS
SELECT 555, 'OFF', 'OFF,1,2,3,4,5,6,7,8,9,10' FROM DUAL UNION ALL
SELECT 666, 'OFF', 'OFF,1,2,3,4,5,6,42,05,0123' FROM DUAL;
Outputs:
ID | VALUE | VALID_VALUES
--: | :---- | :----------------------
555 | OFF | OFF,1,2,3,4,5,6,6,6,6,6
666 | OFF | OFF,1,2,3,4,5,6,6,05,6
db<>fiddle here
You need to split, replace and aggregate as follows:
Select id, value,
Listagg(case when to_number(vals default null on conversion error) is not null
then case when to_number(vals) > 6 then 6 else vals end
else vals end) Within group (order by lvl) as valid_values
From
(Select id, value,
REGEXP_SUBSTR( t.valid_values, '[^,]+', 1, column_value ) ) , ',' ) as vals,
column_value as lvl
from your_table t,
TABLE(CAST(MULTISET(
SELECT level as lvl
FROM DUAL
CONNECT BY LEVEL <= REGEXP_COUNT( t.valid_value, '[^,]+' )
AS SYS.ODCIVARCHAR2LIS ) v
) group by id, value;
For this solution, you need to split using LAG analytic function, before replacing and aggregating as below :
select ID, VALUE
, listagg(
case when regexp_like(separate_value, '^\d+$')
then case when separate_value > 6
then '6'
else separate_value
end
else separate_value
end
, ',') within group (order by lvl) VALID_VALUES
from (
select ID, VALUE
, lvl, substr(VALID_VALUES, lag(pos, 1, 0)over(order by lvl)+1, pos - lag(pos, 1, 0)over(order by lvl)-1) separate_value
from (
select ID, VALUE, VALID_VALUES||','VALID_VALUES, level lvl, instr(VALID_VALUES||',', ',', 1, level)pos
from your_table
connect by level <= length(VALID_VALUES||',')-length(replace(VALID_VALUES||',', ','))
)
)
group by ID, VALUE
;

Sorting comma delimited datasets in row

This is what is given
Numbers Powers
4,5,1 WATER,FIRE
6,3,9 ICE,WATER,FIRE
My requirement is (sorted order)
Numbers Powers
1,4,5 FIRE,WATER
3,6,9 FIRE,ICE,WATER .
I want it in sorted order! How to do it in database?
Split column to rows, then aggregate them back, sorted.
SQL> with test (id, num, pow) as
2 (select 1, '4,5,1', 'water,fire' from dual union all
3 select 2, '6,3,9', 'ice,water,fire' from dual
4 ),
5 temp as
6 -- split columns to rows
7 (select id,
8 regexp_substr(num, '[^,]+', 1, column_value) num1,
9 regexp_substr(pow, '[^,]+', 1, column_value) pow1
10 from test join table(cast(multiset(select level from dual
11 connect by level <= regexp_count(num, ',') + 1
12 ) as sys.odcinumberlist)) on 1 = 1
13 )
14 -- aggregate them back, sorted
15 select id,
16 listagg(num1, ',') within group (order by to_number(num1)) num_result,
17 listagg(pow1, ',') within group (order by pow1) pow_result
18 from temp
19 group by id;
ID NUM_RESULT POW_RESULT
---------- ------------------------------ ------------------------------
1 1,4,5 fire,water
2 3,6,9 fire,ice,water
SQL>
Oracle Setup:
CREATE TABLE test_data ( Numbers, Powers ) AS
SELECT '4,5,1', 'WATER,FIRE' FROM DUAL UNION ALL
SELECT '6,3,9', 'ICE,WATER,FIRE' FROM DUAL UNION ALL
SELECT '7', 'D,B,E,C,A' FROM DUAL
Query:
SELECT (
SELECT LISTAGG( TO_NUMBER( REGEXP_SUBSTR( t.numbers, '\d+', 1, LEVEL ) ), ',' )
WITHIN GROUP ( ORDER BY TO_NUMBER( REGEXP_SUBSTR( t.numbers, '\d+', 1, LEVEL ) ) )
FROM DUAL
CONNECT BY LEVEL <= REGEXP_COUNT( t.numbers, ',' ) + 1
) AS numbers,
(
SELECT LISTAGG( REGEXP_SUBSTR( t.powers, '[^,]+', 1, LEVEL ), ',' )
WITHIN GROUP ( ORDER BY REGEXP_SUBSTR( t.powers, '[^,]+', 1, LEVEL ) )
FROM DUAL
CONNECT BY LEVEL <= REGEXP_COUNT( t.powers, ',' ) + 1
) AS numbers
FROM test_data t
Output:
NUMBERS | NUMBERS
:------ | :-------------
1,4,5 | FIRE,WATER
3,6,9 | FIRE,ICE,WATER
7 | A,B,C,D,E
db<>fiddle here
You can try the following:
I have used the table as I will need some value to get a distinct value. here I have used ROWID.
SELECT
ID,
LISTAGG(NUM, ',') WITHIN GROUP(
ORDER BY
NUM
) AS NUM,
LISTAGG(POW, ',') WITHIN GROUP(
ORDER BY
POW
) AS POW
FROM
(
SELECT
DISTINCT ROWID,
ID,
REGEXP_SUBSTR(NUM, '[^,]+', 1, LEVEL) NUM,
REGEXP_SUBSTR(POW, '[^,]+', 1, LEVEL) POW
FROM
TEST
CONNECT BY REGEXP_SUBSTR(NUM, '[^,]+', 1, LEVEL) IS NOT NULL
OR REGEXP_SUBSTR(POW, '[^,]+', 1, LEVEL) IS NOT NULL
)
GROUP BY ID
ORDER BY ID;
db<>fiddle demo
Cheers!!
----
UPDATE
----
As mentioned in a comment that it is generating duplicates, I have re-framed the whole query as following:
SELECT
ID,
LISTAGG(C_S.NUM, ',') WITHIN GROUP(
ORDER BY
C_S.NUM
) AS NUM,
LISTAGG(C_S.POW, ',') WITHIN GROUP(
ORDER BY
C_S.POW
) AS POW
FROM
(SELECT
T.ID,
REGEXP_SUBSTR(T.NUM, '[^,]+', 1, NUMS_COMMA.COLUMN_VALUE) NUM,
REGEXP_SUBSTR(T.POW, '[^,]+', 1, NUMS_COMMA.COLUMN_VALUE) POW
FROM
TEST T,
TABLE ( CAST(MULTISET(
SELECT
LEVEL
FROM
DUAL
CONNECT BY
LEVEL <= GREATEST(LENGTH(REGEXP_REPLACE(T.NUM, '[^,]+')),
LENGTH(REGEXP_REPLACE(T.POW, '[^,]+'))) + 1
) AS SYS.ODCINUMBERLIST) ) NUMS_COMMA) C_S
GROUP BY ID;
db<>fiddle demo updated
Cheers!!

Oracle/SQL - Need query that will select max value from string in each row

I need a graceful way to select the max value from a field holding a comma delimited list.
Expected Values:
List_1 | Last
------ | ------
A,B,C | C
B,D,C | D
I'm using the following query and I'm not getting what's expected.
select
list_1,
(
select max(values) WITHIN GROUP (order by 1)
from (
select
regexp_substr(list_1,'[^,]+', 1, level) as values
from dual
connect by regexp_substr(list_1, '[^,]+', 1, level) is not null)
) as last
from my_table
Anyone have any ideas to fix my query?
with
test_data ( id, list_1 ) as (
select 101, 'A,B,C' from dual union all
select 102, 'B,D,C' from dual union all
select 105, null from dual union all
select 122, 'A' from dual union all
select 140, 'A,B,B' from dual
)
-- end of simulated table (for testing purposes only, not part of the solution)
select id, list_1, max(token) as max_value
from ( select id, list_1,
regexp_substr(list_1, '([^,])(,|$)', 1, level, null, 1) as token
from test_data
connect by level <= 1 + regexp_count(list_1, ',')
and prior id = id
and prior sys_guid() is not null
)
group by id, list_1
order by id
;
ID LIST_1_ MAX_VAL
---- ------- -------
101 A,B,C C
102 B,D,C D
105
122 A A
140 A,B,B B
In Oracle 12.1 or higher, this can be re-written using the LATERAL clause:
select d.id, d.list_1, x.max_value
from test_data d,
lateral ( select max(regexp_substr(list_1, '([^,]*)(,|$)',
1, level, null, 1)) as max_value
from test_data x
where x.id = d.id
connect by level <= 1 + regexp_count(list_1, ',')
) x
order by d.id
;

How to replace comma separated text values in a column in Oracle?

I have a table t1 with a varchar col V_RELNIST_SKEY which contains comma separated numbers between 1 and 12 as shown. I want to write a select statement to replace numbers by string. For e.g., value 5,6 should be replaced by five,six and so on.
|V_RELNIST_SKEY|
|6 |
|5,6 |
|1,12 |
|1,2,3,12 |
Oracle Setup:
CREATE TABLE test_data ( value ) as
SELECT '9' FROM DUAL UNION ALL
SELECT '6' FROM DUAL UNION ALL
SELECT '1' FROM DUAL UNION ALL
SELECT '2,3' FROM DUAL UNION ALL
SELECT '5,6,7' FROM DUAL UNION ALL
SELECT '8,4' FROM DUAL UNION ALL
SELECT '1,2,3,4,5,6,7,8,9,10,11,12' FROM DUAL;
Query:
SELECT value,
column_value AS words
FROM test_data t
CROSS JOIN
TABLE(
CAST(
MULTISET(
SELECT LISTAGG(
TO_CHAR(
TO_DATE(
REGEXP_SUBSTR( t.value, '\d+', 1, LEVEL ),
'J'
),
'JSP'
),
','
) WITHIN GROUP ( ORDER BY LEVEL )
FROM DUAL
CONNECT BY LEVEL <= REGEXP_COUNT( t.value, '\d+' )
) AS SYS.ODCIVARCHAR2LIST
)
) w;
Output:
VALUE WORDS
-------------------------- ----------------------------------------
9 NINE
6 SIX
1 ONE
2,3 TWO,THREE
5,6,7 FIVE,SIX,SEVEN
8,4 EIGHT,FOUR
1,2,3,4,5,6,7,8,9,10,11,12 ONE,TWO,THREE,FOUR,FIVE,SIX,SEVEN,EIGHT,
NINE,TEN,ELEVEN,TWELVE
Update
What if I have to replace 1 with A, 2 with B, 3 with C and so on?
SELECT value,
COLUMN_VALUE AS words
FROM test_data t
CROSS JOIN
TABLE(
CAST(
MULTISET(
SELECT LISTAGG(
CHR( 64 + REGEXP_SUBSTR( t.value, '\d+', 1, LEVEL ) ),
','
) WITHIN GROUP ( ORDER BY LEVEL )
FROM DUAL
CONNECT BY LEVEL <= REGEXP_COUNT( t.value, '\d+' )
) AS SYS.ODCIVARCHAR2LIST
)
) w;
Output:
VALUE WORDS
-------------------------- ----------------------------------------
9 I
6 F
1 A
2,3 B,C
5,6,7 E,F,G
8,4 H,D
1,2,3,4,5,6,7,8,9,10,11,12 A,B,C,D,E,F,G,H,I,J,K,L

Retrieve records from a specific column in oracle

S.NO id Pid
1 123 PAQ123
2 433 WSD3FF
3 565 PAS45E
4 123 PAQ123X
5 433 WSD3FFY
6 123 PAQ123Z
suppose the above is the sample records in the database.
Now I want to find out in the database whether there is any word (example PAQ123) which is repeating with some prefixes/suffixes like in (PAQ123X,PAQ123Z).
How can I write a query which would result into the above list scenario?
Oracle Setup:
CREATE TABLE table_name ( S_NO, id, Pid ) AS
SELECT 1, 123, 'PAQ123' FROM DUAL UNION ALL
SELECT 2, 433, 'WSD3FF' FROM DUAL UNION ALL
SELECT 3, 565, 'PAS45E' FROM DUAL UNION ALL
SELECT 4, 123, 'PAQ123X' FROM DUAL UNION ALL
SELECT 5, 433, 'WSD3FFY' FROM DUAL UNION ALL
SELECT 6, 123, 'PAQ123Z' FROM DUAL;
Query:
SELECT *
FROM (
SELECT t.*,
( SELECT COUNT(*)
FROM table_name x
WHERE t.id = x.id
AND LENGTH( t.Pid ) < LENGTH( x.pid )
AND INSTR( x.Pid, t.Pid ) = 1 ) AS num_matches
FROM Table_name t
)
WHERE num_matches > 0;
Output:
S_NO ID PID NUM_MATCHES
---------- ---------- ------- -----------
1 123 PAQ123 2
2 433 WSD3FF 1
If you want to get the matches then you can use a collection:
CREATE TYPE stringlist AS TABLE OF VARCHAR2(100);
/
Query:
SELECT *
FROM (
SELECT t.*,
CAST(
MULTISET(
SELECT PID
FROM table_name x
WHERE t.id = x.id
AND LENGTH( t.Pid ) < LENGTH( x.pid )
AND INSTR( x.Pid, t.Pid ) = 1
)
AS stringlist
) AS matches
FROM Table_name t
)
WHERE matches IS NOT EMPTY;
or (since I'm not sure MULTISET is in 10g):
SELECT *
FROM (
SELECT t.*,
CAST(
(
SELECT COLLECT( PID )
FROM table_name x
WHERE t.id = x.id
AND LENGTH( t.Pid ) < LENGTH( x.pid )
AND INSTR( x.Pid, t.Pid ) = 1
)
AS stringlist
) AS matches
FROM Table_name t
)
WHERE matches IS NOT EMPTY;
Output
S_NO ID PID MATCHES
---------- ---------- ------- ------------------------------------
1 123 PAQ123 TEST.STRINGLIST('PAQ123X','PAQ123Z')
2 433 WSD3FF TEST.STRINGLIST('WSD3FFY')