modify column value if condition in oracle - sql

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
;

Related

Display 1,2,3,4 as 1-4 in oracle pl/sql

I have a requirement as- if the user selects checkboxes containing 1,2,3,4 of type 1 and 2,3 of type2 then I should display as (1-4)type1 , (2-3)type2 as the output. We have to do it in backend. I have used LISTAGG but couldn't achieve the desired output. The user selects the checkbox and we have the values stored in Oracle database. Any inputs will be greatly helpful.
For Ex, the following is my data.
Type
Options
1
1
1
2
2
2
1
3
2
3
1
4
Using LISTAGG I could obtain :
select
Type ,
listagg (Option, ',')
WITHIN GROUP
(ORDER BY Type) selectedOption from ( select .... )
Type
selectedOption
1
1,2,3,4
2
2,3
Desired Output:
Type
selectedOption
1
1-4
2
2-3
You can use MATCH_RECOGNIZE to find the range boundaries and then aggregate:
SELECT type,
LISTAGG(
CASE
WHEN range_start = range_end
THEN TO_CHAR( range_start )
ELSE range_start || '-' || range_end
END,
','
) WITHIN GROUP ( ORDER BY mn ) AS grouped_values
FROM table_name
MATCH_RECOGNIZE(
PARTITION BY type
ORDER BY value
MEASURES
FIRST( value ) AS range_start,
LAST( value ) AS range_end,
MATCH_NUMBER() AS mn
ONE ROW PER MATCH
PATTERN ( successive_values* last_value )
DEFINE successive_values AS NEXT( value ) <= LAST( value ) + 1
)
GROUP BY type
Which, for the sample data:
CREATE TABLE table_name ( type, value ) AS
SELECT 1, COLUMN_VALUE
FROM TABLE( SYS.ODCINUMBERLIST( 1, 2, 10, 17, 3, 15, 4, 16, 5, 7, 8 ) )
UNION ALL
SELECT 2, COLUMN_VALUE
FROM TABLE( SYS.ODCINUMBERLIST( 2, 3 ) )
Outputs:
TYPE | GROUPED_VALUES
---: | :---------------
1 | 1-5,7-8,10,15-17
2 | 2-3
db<>fiddle here

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!!

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

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

regexp_substr to bring back data before a foward slash

I have the following pattern of characters in a dataset. I need to manipulate the data & cross refer it to another table. I'm trying to write a regexp_substr to bring back data before a foward slash starting from the left. for example:-
abc/ab/123/zzz
so I need to get the following results back to then compare to another table
abc
abc/ab
abc/ab/123
I have worked out the other logic but an struggling with the various regexp.
Here is the recursive query with SUBSTR and INSTR:
with cte(col) as
(
select substr(col, 1, instr(col, '/', -1) - 1) from mytable
union all
select substr(col, 1, instr(col, '/', -1) - 1) from cte where instr(col, '/') > 0
)
select col from cte;
And here is the query with REGEXP_REPLACE:
with cte(col) as
(
select regexp_replace(col, '/[^/]*$', '') from mytable
union all
select regexp_replace(col, '/[^/]*$', '') from cte where instr(col, '/') > 0
)
select col from cte;
You don't need a regular expression. You can do it with (faster) string functions:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE test_data ( id, value ) AS
SELECT 1, 'abc/ab/123/zzz' FROM DUAL;
Query 1:
WITH bounds ( id, value, end_pos ) AS (
SELECT id,
value,
INSTR( value, '/', 1 )
FROM test_data
WHERE INSTR( value, '/', 1 ) > 0
UNION ALL
SELECT id,
value,
INSTR( value, '/', end_pos + 1 )
FROM bounds
WHERE INSTR( value, '/', end_pos + 1 ) > 0
)
SELECT id,
SUBSTR( value, 1, end_pos ) AS item
FROM bounds
ORDER BY id, end_pos
Results:
| ID | ITEM |
|----|-------------|
| 1 | abc/ |
| 1 | abc/ab/ |
| 1 | abc/ab/123/ |
However, if you did want to use regular expressions then you could do:
Query 2:
WITH bounds ( id, value, lvl, item ) AS (
SELECT id,
value,
1,
REGEXP_SUBSTR( value, '.*?/', 1, 1 )
FROM test_data
WHERE REGEXP_SUBSTR( value, '.*?/', 1, 1 ) IS NOT NULL
UNION ALL
SELECT id,
value,
lvl + 1,
item || REGEXP_SUBSTR( value, '.*?/', 1, lvl + 1 )
FROM bounds
WHERE REGEXP_SUBSTR( value, '.*?/', 1, lvl + 1 ) IS NOT NULL
)
SELECT id,
item
FROM bounds
Results:
| ID | ITEM |
|----|-------------|
| 1 | abc/ |
| 1 | abc/ab/ |
| 1 | abc/ab/123/ |

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
;