Update based on the comma seperated value - sql

I have the below Table with two columns both columns are VARCHAR2(100).
PARAM_NAME PARAM_VALUE
PlanName,DemandMonth EUMOCP,01-2022
PlanName,DemandMonth EUMOCP,02-2022
PlanName,DemandMonth EUMOCP,03-2022
PlanName,DemandMonth EUMOCP,04-2021
How can we write a update on the table so that it only updates the corresponding value.
For example:
Update DemandMonth from 01-2022 to 04-2022.
Provided it only updates the columns based on the first column
For instance,
Column A Column B
1,2 3,4
based on 1 we can update 3 as it is before ',' similarly based on 2 we can update 4.
What we want to achieve is the first it identifies where is 'DemandMonth' and then accordingly update the second column. Also if possible can we write it for 4 or 5 comma seperated values?

Don't store values in delimited strings.
Change your table so the values are:
CREATE TABLE params ( id, param_name, param_value ) AS
SELECT 1, 'PlanName', 'EUMOCP' FROM DUAL UNION ALL
SELECT 1, 'DemandMonth', '01-2022' FROM DUAL UNION ALL
SELECT 2, 'PlanName', 'EUMOCP' FROM DUAL UNION ALL
SELECT 2, 'DemandMonth', '02-2022' FROM DUAL UNION ALL
SELECT 3, 'PlanName', 'EUMOCP' FROM DUAL UNION ALL
SELECT 3, 'DemandMonth', '03-2022' FROM DUAL UNION ALL
SELECT 4, 'PlanName', 'EUMOCP' FROM DUAL UNION ALL
SELECT 4, 'DemandMonth', '04-2021' FROM DUAL;
Then all you need to do to update the value is:
UPDATE params
SET param_value = '04-2022'
WHERE param_name = 'DemandMonth'
AND param_value = '01-2022';
There is no worrying about where in the delimited string the value is and it is all simple.

You should not do this and should refactor your table to not use delimited strings... however, you can use:
MERGE INTO params dst
USING (
WITH items ( rid, param_names, param_values, name, value, lvl, max_lvl ) AS (
SELECT ROWID,
param_name,
param_value,
REGEXP_SUBSTR( param_name, '[^,]+', 1, 1 ),
REGEXP_SUBSTR( param_value, '[^,]+', 1, 1 ),
1,
REGEXP_COUNT( param_value, '[^,]+' )
FROM params
UNION ALL
SELECT rid,
param_names,
param_values,
REGEXP_SUBSTR( param_names, '[^,]+', 1, lvl + 1 ),
REGEXP_SUBSTR( param_values, '[^,]+', 1, lvl + 1 ),
lvl + 1,
max_lvl
FROM items
WHERE lvl < max_lvl
)
SELECT rid,
LISTAGG(
CASE
WHEN name = 'DemandMonth' AND value = '01-2022'
THEN '04-2022'
ELSE value
END,
','
) WITHIN GROUP ( ORDER BY lvl ) AS param_value
FROM items
GROUP BY rid
HAVING COUNT(
CASE
WHEN name = 'DemandMonth' AND value = '01-2022'
THEN 1
END
) > 0
) src
ON ( dst.ROWID = src.rid )
WHEN MATCHED THEN
UPDATE SET param_value = src.param_value;
Which, for the sample data:
CREATE TABLE params ( param_name, param_value ) AS
SELECT 'PlanName,DemandMonth', 'EUMOCP,01-2022' FROM DUAL UNION ALL
SELECT 'PlanName,DemandMonth', 'EUMOCP,02-2022' FROM DUAL UNION ALL
SELECT 'PlanName,DemandMonth', 'EUMOCP,03-2022' FROM DUAL UNION ALL
SELECT 'PlanName,DemandMonth', 'EUMOCP,04-2021' FROM DUAL;
Then:
SELECT * FROM params;
Outputs:
PARAM_NAME
PARAM_VALUE
PlanName,DemandMonth
EUMOCP,04-2022
PlanName,DemandMonth
EUMOCP,02-2022
PlanName,DemandMonth
EUMOCP,03-2022
PlanName,DemandMonth
EUMOCP,04-2021
db<>fiddle here

Related

Replace some variables by data of another table in sql oracle

I have a table with two columns
type
TXT
A
this is some text for %1 and %2
B
this is another step for %1
in a translation table I have the signification of the variables %X that looks like
Type
variable
descr
A
%1
#person1#
A
%2
#person2#
B
%1
#manager#
I want to replace in my first table all the variables by the description, so the result has to looks like this:
type
TXT
A
this is some text for #person1# and #person2#
B
this is another step for #manager#
I tried with a replace, but I didn't figured out how to make it work
To replace all variables you could use a recursive algorithm:
with data(typ, txt) as (
select 'A', 'this is some text for %1 and %2' from dual union all
select 'B', 'this is another step for %1' from dual
),
translations(typ, var, description) as (
select 'A', '%1', '#person1#' from dual union all
select 'A', '%2', '#person2#' from dual union all
select 'B', '%1', '#manager#' from dual -- union all
),
rtranslations(typ, var, description,rn) as (
select t.*, row_number() over(partition by typ order by var) as rn
from translations t
),
replacecte(typ, txt, replaced_txt, rn) as (
select d.typ, d.txt, replace(d.txt, t.var, t.description), t.rn
from data d
join rtranslations t on t.typ = d.typ
where t.rn = 1
union all
select r.typ, r.txt, replace(r.replaced_txt, t.var, t.description), t.rn
from replacecte r
join rtranslations t on t.typ = r.typ and t.rn = r.rn + 1
)
select r.typ, r.txt, replaced_txt from replacecte r
where rn = length(txt) - length(replace(txt,'%',''))
;
You can also do it this way without recursion. data and descr are of course just mock ups for your tables, you would not need any WITH clauses. This method uses the steps (1) break up the sentences into words, (2) outer join using those words to your description table, replacing any matches with the description values, (3) reassemble the words back into sentences using LISTAGG.
WITH data AS(SELECT 'A' type, 'this is some text for %1 and %2' txt FROM dual
UNION ALL
SELECT 'B' type, 'this is another step for %1' txt FROM dual
),
descr AS (SELECT 'A' type, '%1' variable,'#person1#' description FROM dual
UNION ALL
SELECT 'A' type, '%2' variable,'#person2#' description FROM dual
UNION ALL
SELECT 'B' type, '%1' variable,'#manager#' description FROM dual)
SELECT type,
LISTAGG(new_word,' ') WITHIN GROUP (ORDER BY seq) txt
FROM (SELECT x.type,
NVL(descr.description,x.word) new_word,
seq
FROM (SELECT type,SUBSTR(' '||txt,INSTR(' '||txt,' ',1,seq)+1,INSTR(' '||txt||' ',' ',1,seq+1) - (INSTR(' '||txt,' ',1,seq)+1)) word,seq
FROM data,
(SELECT ROWNUM seq FROM dual CONNECT BY LEVEL <= 50) x) x,
descr
WHERE x.type = descr.type(+)
AND x.word = descr.variable(+))
GROUP BY type
You could use PIVOT to get the var values from rows into columns (geting all vars in the same row with text) and then do multiple replaces depending on number of var values:
SELECT t.A_TYPE,
CASE WHEN d.V3 Is Not Null THEN REPLACE(REPLACE(REPLACE(t.TXT, '%1', d.V1), '%2', d.V2), '%3', d.V3)
WHEN d.V2 Is Not Null THEN REPLACE(REPLACE(t.TXT, '%1', d.V1), '%2', d.V2)
WHEN d.V1 Is Not Null THEN REPLACE(t.TXT, '%1', d.V1)
ELSE t.TXT
END "TXT"
FROM tbl t
INNER JOIN ( SELECT *
FROM ( Select A_TYPE, VAR, DESCRIPTION FROM descr )
PIVOT ( MAX(DESCRIPTION) For VAR IN('%1' "V1", '%2' "V2", '%' "V3") )
) d ON(d.A_TYPE = t.A_TYPE)
With sample data as:
WITH
tbl (A_TYPE, TXT) AS
(
Select 'A', 'this is some text for %1 and %2' From Dual Union All
Select 'B', 'this is another step for %1' From dual
),
descr (A_TYPE, VAR, DESCRIPTION) AS
(
Select 'A', '%1', '#person1#' From Dual UNION ALL
Select 'A', '%2', '#person2#' From Dual UNION ALL
Select 'B', '%1', '#manager#' From Dual
)
... the result should be
A_TYPE TXT
------ -----------------------------------------------
A this is some text for #person1# and #person2#
B this is another step for #manager#

Convert a series of Number values in Text in Oracle SQL Query

In the Oracle database, I have string values (VARCHAR2) like 1,4,7,8. The number represents as 1=car, 2= bus, 3=BB, 4=SB, 5=Ba, 6=PA, 7=HB, and 8 =G
and want to convert the above-said example to "car,SB,HB,G" in my query results
I tried to use "Decode" but it does not work. Please advise how to make it works. Would appreciate.
Thanks`
Initially, I have used the following query:
Select Clientid as C#, vehicletypeExclusions as vehicle from
clients
The sample of outcomes are:
C# Vehicle
20 1,19,20,23,24,7,5
22 1,19,20,23,24,7,5
I also tried the following that gives me the null value of vehicles:
Select Clientid as C#, Decode (VEHICLETYPEEXCLUSIONS, '1', 'car',
'3','bus', '5','ba' ,'7','HB', '8','G'
, '9','LED1102', '10','LED1104', '13','LED8-2',
'14','Flip4-12', '17','StAT1003', '19','Taxi-Min', '20','Tax_Sed',
'21','Sup-veh' , '22','T-DATS', '23','T-Mini',
'24','T-WAM') as vehicle_Ex from clients >
Here's one option. Read comments within code. Sample data in lines #1 - 13; query begins at line #14.
SQL> with
2 expl (id, name) as
3 (select 1, 'car' from dual union all
4 select 2, 'bus' from dual union all
5 select 3, 'BB' from dual union all
6 select 4, 'SB' from dual union all
7 select 5, 'Ba' from dual union all
8 select 6, 'PA' from dual union all
9 select 7, 'HB' from dual union all
10 select 8, 'G' from dual
11 ),
12 temp (col) as
13 (select '1,4,7,8' from dual),
14 -- split COL to rows
15 spl as
16 (select regexp_substr(col, '[^,]+', 1, level) val,
17 level lvl
18 from temp
19 connect by level <= regexp_count(col, ',') + 1
20 )
21 -- join SPL with EXPL; aggregate the result
22 select listagg(e.name, ',') within group (order by s.lvl) result
23 from expl e join spl s on s.val = e.id;
RESULT
--------------------------------------------------------------------------------
car,SB,HB,G
SQL>
Using the function f_subst from https://stackoverflow.com/a/68537479/429100 :
create or replace
function f_subst(str varchar2, template varchar2, subst sys.odcivarchar2list) return varchar2
as
res varchar2(32767):=str;
begin
for i in 1..subst.count loop
res:=replace(res, replace(template,'%d',i), subst(i));
end loop;
return res;
end;
/
I've replaced ora_name_list_t (nested table) with sys.odcivarchar2list (varray) to make this example easier, but I would suggest to create your own collection for example create type varchar2_table as table of varchar2(4000);
Example:
select
f_subst(
'1,4,7,8'
,'%d'
,sys.odcivarchar2list('car','bus','BB','SB','Ba','PA','HB','G')
) s
from dual;
S
----------------------------------------
car,SB,HB,G
Assume you have a lookup table (associating the numeric codes with descriptions) and a table of input strings, which I called sample_inputs in my tests, as shown below:
create table lookup (code, descr) as
select 1, 'car' from dual union all
select 2, 'bus' from dual union all
select 3, 'BB' from dual union all
select 4, 'SB' from dual union all
select 5, 'Ba' from dual union all
select 6, 'PA' from dual union all
select 7, 'HB' from dual union all
select 8, 'G' from dual
;
create table sample_inputs (str) as
select '1,4,7,8' from dual union all
select null from dual union all
select '3' from dual union all
select '5,5,5' from dual union all
select '6,2,8' from dual
;
One strategy for solving your problem is to split the input - slightly modified to make it a JSON array, so that we can use json_table to split it - then join to the lookup table and re-aggregate.
select s.str, l.descr_list
from sample_inputs s cross join lateral
( select listagg(descr, ',') within group (order by ord) as descr_list
from json_table( '[' || str || ']', '$[*]'
columns code number path '$', ord for ordinality)
join lookup l using (code)
) l
;
STR DESCR_LIST
------- ------------------------------
1,4,7,8 car,SB,HB,G
3 BB
5,5,5 Ba,Ba,Ba
6,2,8 PA,bus,G

Oracle SQL find missing sequence in varchar2 field

I am new to oracle and to this forum. I have searched and found answers on how to do this with a column of just numbers but this has txt at the beginning then a sequenced number.
I have a table that has a varchar2 column named myid which has characters with a number at the end which is in order the number at the end is always 6 digits with leading zeros.
Hello_002190
Hello_002188
Bye_000187
Bye_000185
Bye_000184
Get_008133
Get_008131
Gone_001112
Gone_001110
Gone_001109
I need an Oracle SQL script that will show me all the missing rows.
The result for the above should be:
Hello_002189
Bye_000186
Get_008132
Gone_001111
Thanks in advance for the help
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE table_name ( value ) AS
SELECT 'Hello_002190' FROM DUAL UNION ALL
SELECT 'Hello_002188' FROM DUAL UNION ALL
SELECT 'Bye_000187' FROM DUAL UNION ALL
SELECT 'Bye_000185' FROM DUAL UNION ALL
SELECT 'Bye_000184' FROM DUAL UNION ALL
SELECT 'Get_008133' FROM DUAL UNION ALL
SELECT 'Get_008131' FROM DUAL UNION ALL
SELECT 'Gone_001112' FROM DUAL UNION ALL
SELECT 'Gone_001110' FROM DUAL UNION ALL
SELECT 'Gone_001109' FROM DUAL;
Query 1:
WITH data ( prefix, suffix ) AS (
SELECT SUBSTR( value, 1, INSTR( value, '_' ) ),
TO_NUMBER( SUBSTR( value, INSTR( value, '_' ) + 1 ) )
FROM table_name
),
bounds ( prefix, min_suffix, max_suffix ) AS (
SELECT prefix, MIN( suffix ), MAX( suffix )
FROM data
GROUP BY prefix
)
SELECT prefix || TO_CHAR( column_value, 'FM000000' ) AS missing_value
FROM bounds b
CROSS JOIN
TABLE(
CAST(
MULTISET(
SELECT b.min_suffix + LEVEL - 1
FROM DUAL
CONNECT BY b.min_suffix + LEVEL - 1 <= b.max_suffix
) AS SYS.ODCINUMBERLIST
)
)
MINUS
SELECT value FROM table_name
Results:
| MISSING_VALUE |
|---------------|
| Bye_000186 |
| Get_008132 |
| Gone_001111 |
| Hello_002189 |

Remove duplicate values from comma separated string in Oracle

I need your help with the regexp_replace function. I have a table which has a column for concatenated string values which contain duplicates. How do I eliminate them?
Example:
Ian,Beatty,Larry,Neesha,Beatty,Neesha,Ian,Neesha
I need the output to be
Ian,Beatty,Larry,Neesha
The duplicates are random and not in any particular order.
Update--
Here's how my table looks
ID Name1 Name2 Name3
1 a b c
1 c d a
2 d e a
2 c d b
I need one row per ID having distinct name1,name2,name3 in one row as a comma separated string.
ID Name
1 a,c,b,d,c
2 d,c,e,a,b
I have tried using listagg with distinct but I'm not able to remove the duplicates.
The easiest option I would go with -
SELECT ID, LISTAGG(NAME_LIST, ',')
FROM (SELECT ID, NAME1 NAME_LIST FROM DATA UNION
SELECT ID, NAME2 FROM DATA UNION
SELECT ID, NAME3 FROM DATA
)
GROUP BY ID;
Demo.
So, try this out...
([^,]+),(?=.*[A-Za-z],[] ]*\1)
I don't think you can do it just with regexp_replace if the repeated values are not next to each other. One approach is to split the values up, eliminate the duplicates, and then put them back together.
The common method to tokenize a delimited string is with regexp_substr and a connect by clause. Using a bind variable with your string to make the code a bit clearer:
var value varchar2(100);
exec :value := 'Ian,Beatty,Larry,Neesha,Beatty,Neesha,Ian,Neesha';
select regexp_substr(:value, '[^,]+', 1, level) as value
from dual
connect by regexp_substr(:value, '[^,]+', 1, level) is not null;
VALUE
------------------------------
Ian
Beatty
Larry
Neesha
Beatty
Neesha
Ian
Neesha
You can use that as a subquery (or CTE), get the distinct values from it, then reassemble it with listagg:
select listagg(value, ',') within group (order by value) as value
from (
select distinct value from (
select regexp_substr(:value, '[^,]+', 1, level) as value
from dual
connect by regexp_substr(:value, '[^,]+', 1, level) is not null
)
);
VALUE
------------------------------
Beatty,Ian,Larry,Neesha
It's a bit more complicated if you're looking at multiple rows in a table as that confused the connect-by syntax, but you can use a non-determinisitic reference to avoid loops:
with t42 (id, value) as (
select 1, 'Ian,Beatty,Larry,Neesha,Beatty,Neesha,Ian,Neesha' from dual
union all select 2, 'Mary,Joe,Mary,Frank,Joe' from dual
)
select id, listagg(value, ',') within group (order by value) as value
from (
select distinct id, value from (
select id, regexp_substr(value, '[^,]+', 1, level) as value
from t42
connect by regexp_substr(value, '[^,]+', 1, level) is not null
and id = prior id
and prior dbms_random.value is not null
)
)
group by id;
ID VALUE
---------- ------------------------------
1 Beatty,Ian,Larry,Neesha
2 Frank,Joe,Mary
Of course this wouldn't be necessary if you were storing relational data properly; having a delimited string in a column is not a good idea.
There is a way to find duplicates in this case, but it is a problem to remove them if there are more than one duplicated name within a string per id. Here is code that can deal with one duplicate per id.
Sample data:
WITH
tbl AS
(
Select 1 "ID", 'a' "NAME_1", 'b' "NAME_2", 'c' "NAME_3" From Dual Union All
Select 1 "ID", 'c' "NAME_1", 'd' "NAME_2", 'a' "NAME_3" From Dual Union All
Select 2 "ID", 'd' "NAME_1", 'e' "NAME_2", 'a' "NAME_3" From Dual Union All
Select 2 "ID", 'c' "NAME_1", 'd' "NAME_2", 'b' "NAME_3" From Dual
),
lists AS
(
Select 1 "ID", 'a,c,b,d,c' "NAME" From Dual Union All
Select 2 "ID", 'd,c,e,a,b' "NAME" From Dual
),
Creating CTE that compares your LISTAGG sttring with original data finding duplicate values:
grid AS
(
Select DISTINCT l.ID, l.NAME,
CASE WHEN ( Length(l.NAME || ',') - Length(Replace(l.NAME || ',', t.NAME_1 || ',', '')) ) / Length(t.NAME_1 || ',') > 1 THEN NAME_1 END "NAME_1",
CASE WHEN ( Length(l.NAME || ',') - Length(Replace(l.NAME || ',', t.NAME_2 || ',', '')) ) / Length(t.NAME_2 || ',') > 1 THEN NAME_2 END "NAME_2",
CASE WHEN ( Length(l.NAME || ',') - Length(Replace(l.NAME || ',', t.NAME_3 || ',', '')) ) / Length(t.NAME_3 || ',') > 1 THEN NAME_3 END "NAME_3"
From
lists l
Inner Join
tbl t ON(t.ID = l.ID)
)
ID NAME NAME_1 NAME_2 NAME_3
---------- --------- ------ ------ ------
2 d,c,e,a,b
1 a,c,b,d,c c
1 a,c,b,d,c c
Main SQL, using Union, builds new string (removing second appearance) where the duplicate was found and then puts that new string after comparison with the old one.
SELECT DISTINCT l.ID, Nvl(g.NAME, l.NAME) NAME
FROM
lists l
LEFT JOIN
(
SELECT ID, CASE WHEN NAME_1 Is Not Null
THEN REPLACE(NAME, NAME, COALESCE( REPLACE( SubStr(NAME, 1, InStr(NAME, NAME_1, 1, 2) - 1) || SubStr(NAME, InStr(NAME, NAME_1, 1, 2) + Length(NAME_1)), ',,', ','), NULL ) )
END "NAME"
FROM grid
WHERE COALESCE(NAME_1, NAME_2, NAME_3) IS NOT NULL
UNION ALL
SELECT ID, CASE WHEN NAME_2 Is Not Null
THEN REPLACE(NAME, NAME, COALESCE( REPLACE( SubStr(NAME, 1, InStr(NAME, NAME_2, 1, 2) - 1) || SubStr(NAME, InStr(NAME, NAME_2, 1, 2) + Length(NAME_2)), ',,', ','), NULL ) )
END "NAME"
FROM grid
WHERE COALESCE(NAME_1, NAME_2, NAME_3) IS NOT NULL
UNION ALL
SELECT ID, CASE WHEN NAME_3 Is Not Null
THEN REPLACE(NAME, NAME, COALESCE( REPLACE( SubStr(NAME, 1, InStr(NAME, NAME_3, 1, 2) - 1) || SubStr(NAME, InStr(NAME, NAME_3, 1, 2) + Length(NAME_3)), ',,', ','), NULL ) )
END "NAME"
FROM grid
WHERE COALESCE(NAME_1, NAME_2, NAME_3) IS NOT NULL
) g ON(g.ID = l.ID And Length(g.NAME) < Length(l.NAME))
R e s u l t :
ID NAME
---------- -------------
2 d,c,e,a,b
1 a,c,b,d
For multiple occurences within a string or for multiplicated different names there should be done some recursions or multiplied nestings to get it done...

Decode values of a column in plsql

I have table in which there is a column which contains values as 1:2 or 2:3 or 2:3:4 etc. I need to decode these values on the basis of their values mentioned in the different table. like 1 is x and 2 is y.
there are 5 values in the column. earlier only one value were there where name against that values was being fetched from other table by join condition. But now any combination of 5 values can be there separated by ":"-colon. Please suggest how to get names of these values for a column. Let me know if any other detail is required
Please suggest a way to implement this.
Hi here how you need to go;
first start with inner query it will give all values in you column like 1, 2,3, 4 etc. then need to create mapping table eg. 1 to 'x' , 2 to 'y' physically or logically as I have done and select from mapping table as per result from inner query which is your spitted column values.
with map_data as (
select 1 as d_value , 'X' as m_value from dual
union all
select 2 as d_value , 'Y' as m_value from dual
union all
select 3 as d_value , 'Z' as m_value from dual)
select * from map_data
where d_value in(
WITH data AS (
SELECT '1:2:3' AS "value" FROM DUAL
UNION ALL
SELECT '2:4' AS "value" FROM DUAL
)
SELECT REGEXP_SUBSTR( data."value", '[^:]+', 1, levels.COLUMN_VALUE )
FROM data,
TABLE(
CAST(
MULTISET(
SELECT LEVEL
FROM DUAL
CONNECT BY LEVEL <= LENGTH( regexp_replace( "value", '[^:]+')) + 1
) AS sys.OdciNumberList
)
) levels)