Oracle : SQL to replace items in a string with items of another string - sql

I have a column "col1" value like : 'a,b,x,y,z' (ordered string)
Another column "col2" is like : 'a,x' or 'b,y,z' (ordered string)
All the string values in "col2" are generated by a subquery. So it is not constant.
But, the value in "col1" is constant. That is col1='a,b,x,y,z'
create table test (col1 varchar2(20), col2 varchar2(10));
insert into test values ('a,b,x,y,z','a,x');
insert into test values ('a,b,x,y,z','b,y,z');
Need help with the replacing in one sql.
Need help to replace the elements on "col1" with "col2".
For example,
when col2='a,x', the result should be : 'b,y,z'
when col2='b,y,z', the result should be : 'a,x'

Here is a fun way to do this:
select col1, col2,
ltrim(regexp_replace(translate(replace(col1,','),','||replace(col2,','),',')
,'(.)',',\1'),',') as col3
from test;
That is: (reading the function calls as they are executed, from inside out)
Remove the commas from both strings
Use TRANSLATE() to remove the characters of the second string from the first string
Use REGEXP_REPLACE to add commas before each character of the remaining string
Trim the leading comma

Here's one option; I included the ID column to make it simpler. I hope that a column similar to it exists in your real case.
The idea is:
split each column (col1, col2) to rows
CTE one represents col1's rows
CTE two represents col2's rows
using the MINUS set operator, subtract those two sets of rows
using LISTAGG, aggregate the result
SQL> select * From test;
ID COL1 COL2
---------- -------------------- ----------
1 a,b,x,y,z a,x
2 a,b,x,y,z b,y,z
SQL> with
2 one as
3 (select id, regexp_substr(col1, '[^,]+', 1, column_value) col
4 from test,
5 table(cast(multiset(select level from dual
6 connect by level <= regexp_count(col1, ',') + 1
7 ) as sys.odcinumberlist))
8 ),
9 two as
10 (select id, regexp_substr(col2, '[^,]+', 1, column_value) col
11 from test,
12 table(cast(multiset(select level from dual
13 connect by level <= regexp_count(col2, ',') + 1
14 ) as sys.odcinumberlist))
15 ),
16 t_minus as
17 (select id, col from one
18 minus
19 select id, col from two
20 )
21 select id, listagg(col, ',') within group (order by col) result
22 From t_minus
23 group by id;
ID RESULT
---------- --------------------
1 b,y,z
2 a,x
SQL>

Related

looping in sql with delimiter

I just had this idea of how can i loop in sql?
For example
I have this column
PARAMETER_VALUE
E,C;S,C;I,X;G,T;S,J;S,F;C,S;
i want to store all value before (,) in a temp column also store all value after (;) into another column
then it wont stop until there is no more value after (;)
Expected Output for Example
COL1 E S I G S S C
COL2 C C X T J F S
etc . . .
You can get by using regexp_substr() window analytic function with connect by level <= clause
with t1(PARAMETER_VALUE) as
(
select 'E,C;S,C;I,X;G,T;S,J;S,F;C,S;' from dual
), t2 as
(
select level as rn,
regexp_substr(PARAMETER_VALUE,'([^,]+)',1,level) as str1,
regexp_substr(PARAMETER_VALUE,'([^;]+)',1,level) as str2
from t1
connect by level <= regexp_count(PARAMETER_VALUE,';')
)
select listagg( regexp_substr(str1,'([^;]+$)') ,' ') within group (order by rn) as col1,
listagg( regexp_substr(str2,'([^,]+$)') ,' ') within group (order by rn) as col2
from t2;
COL1 COL2
------------- -------------
E S I G S S C C C X T J F S
Demo
Assuming that you need to separate the input into rows, at the ; delimiters, and then into columns at the , delimiter, you could do something like this:
-- WITH clause included to simulate input data. Not part of the solution;
-- use actual table and column names in the SELECT statement below.
with
t1(id, parameter_value) as (
select 1, 'E,C;S,C;I,X;G,T;S,J;S,F;C,S;' from dual union all
select 2, ',U;,;V,V;' from dual union all
select 3, null from dual
)
-- End of simulated input data
select id,
level as ord,
regexp_substr(parameter_value, '(;|^)([^,]*),', 1, level, null, 2) as col1,
regexp_substr(parameter_value, ',([^;]*);' , 1, level, null, 1) as col2
from t1
connect by level <= regexp_count(parameter_value, ';')
and id = prior id
and prior sys_guid() is not null
order by id, ord
;
ID ORD COL1 COL2
--- --- ---- ----
1 1 E C
1 2 S C
1 3 I X
1 4 G T
1 5 S J
1 6 S F
1 7 C S
2 1 U
2 2
2 3 V V
3 1
Note - this is not the most efficient way to split the inputs (nothing will be very efficient - the data model, which is in violation of First Normal Form, is the reason). This can be improved using standard instr and substr, but the query will be more complicated, and for that reason, harder to maintain.
I generated more input data, to illustrate a few things. You may have several inputs that must be broken up at the same time; that must be done with care. (Note the additional conditions in CONNECT BY). I also illustrate the handling of NULL - if a comma comes right after a semicolon, that means that the "column 1" part of that pair must be NULL. That is shown in the output.

Comma-separated string match

I have this query:
SELECT regexp_replace (var_called_num, '^' ||ROUTING_PREFIX) INTO Num
FROM INCOMING_ROUTING_PREFIX
WHERE var_called_num LIKE ROUTING_PREFIX ||'%';`
INCOMING_ROUTING_PREFIX table has two rows
1) 007743
2) 007742
var_called_num is 0077438843212123. So above query gives the result 8843212123.
So basically, the query is removing prefix (longest match from table) from var_called_num.
Now my table has changed. Now it has only 1 row which is comma-separated.
Modified Table:
INCOMING_ROUTING_PREFIX table has one row which is comma-separated:
1) 007743,007742
How to modify the query to achieve the same behavior. Need to remove longest match prefix from var_called_num.
Here's one option: you'd have to split the prefix into rows, and the use it in REGEXP_REPLACE.
SQL> with
2 calnum (var_called_num) as
3 (select '0077438843212123' from dual),
4 incoming_routing_prefix (routing_prefix) as
5 (select '007743,007742' from dual),
6 --
7 irp_split as
8 (select regexp_substr(i.routing_prefix, '[^,]+', 1, level) routing_prefix
9 from incoming_routing_prefix i
10 connect by level <= regexp_count(i.routing_prefix, ',') + 1
11 )
12 select regexp_replace(c.var_called_num, '^' || s.routing_prefix) result
13 from calnum c join irp_split s on s.routing_prefix = substr(c.var_called_num, 1, length(s.routing_prefix));
RESULT
----------------
8843212123
SQL>
By the way, why did you change the model to a worse version than it was before?
you can split the values
with test as (
select regexp_substr('007743,007742','[^,]+', 1, level) as ROUTING_PREFIX from dual
connect by regexp_substr('007743,007742S', '[^,]+', 1, level) is not null
)
and that use the view in your select
SELECT regexp_replace ('0077438843212123', '^' ||ROUTING_PREFIX)
FROM test WHERE '0077438843212123' LIKE ROUTING_PREFIX ||'%';

Compare two delimited strings and return corresponding value in PL SQL

I have two columns with a hashtag delimited value, i.e. Email#Web#Telephone#SMS#MMS & 0#0#0#1#0 Note that each delimited value of the second column matches up with its corresponding delimited value in the first column, i.e. Email = 0, Web = 0, Telephone = 0, SMS = 1 etc.
Based on a parameter, I want to return the matching value of the second column. i.e. incoming param = Web#Telephone#SMS, thus the value that I want to return is 0#0#1.
This need to be done in PL SQL, and I have no clue where to start, which explains the lack of sample code.
Any help please?
There are a couple of very useful utility functions in an Oracle package called APEX_UTIL. (This package concerns Oracle Application Express aka APEX, but can be used anywhere). They are:
apex_util.string_to_table
apex_util.table_to_string
Using string_to_table you can convert the delimited string into a table of values:
declare
v_table apex_application_global.vc_arr2; -- This is the table type apex_util uses
begin
v_table := apex_util.table_to_string ('Email#Web#Telephone#SMS#MMS', '#');
end;
You now have an array with 5 elements ('Email', 'Web', 'Telephone', 'SMS', 'MMS');
You can do the same with the values string to get a table with elements ('0', '0', '0', '1', 0'). And you can do the same with the parameter to get a table with elements ('Web', 'Telephone', 'SMS').
You can then use PL/SQL logic to build a new array with elements for the values you need to return, i.e. ('0', '0', '1'). I have left this part to you!
Finally you can turn that back into a delimited string:
return apex_util.table_to_string (v_return_table, '#');
Firstly, you should normalize the table and have the attributes in different columns rather than delimited strings in a single column.
Anyway, you could do it in many ways using the techniques to Split comma delimited strings in a table
For example, using REGEXP_SUBSTR and CONNECT BY clause:
SQL> WITH DATA(attr, val) AS(
2 SELECT 'Email#Web#Telephone#SMS#MMS', '0#0#0#1#0' FROM dual
3 )
4 SELECT lines.COLUMN_VALUE,
5 trim(regexp_substr(t.attr, '[^#]+', 1, lines.COLUMN_VALUE)) attr,
6 trim(regexp_substr(t.val, '[^#]+', 1, lines.COLUMN_VALUE)) val
7 FROM data t,
8 TABLE (CAST (MULTISET
9 (SELECT LEVEL FROM dual CONNECT BY LEVEL <= regexp_count(t.attr, '#')+1
10 ) AS sys.odciNumberList ) ) lines
11 /
COLUMN_VALUE ATTR VAL
------------ --------------------------- ---------
1 Email 0
2 Web 0
3 Telephone 0
4 SMS 1
5 MMS 0
SQL>
Now, you can get the respective values for each attribute.
You could put the entire logic in a FUNCTION and return the corresponding values of each attribute and call the function in SELECT statement.
For example,
SQL> CREATE OR REPLACE
2 FUNCTION get_val_from_attr(
3 attr_name VARCHAR2)
4 RETURN NUMBER
5 IS
6 var_val NUMBER;
7 BEGIN
8 WITH DATA(attr, val) AS
9 ( SELECT 'Email#Web#Telephone#SMS#MMS', '0#0#0#1#0' FROM dual
10 ),
11 t2 AS
12 (SELECT lines.COLUMN_VALUE,
13 trim(regexp_substr(t.attr, '[^#]+', 1, lines.COLUMN_VALUE)) attr,
14 trim(regexp_substr(t.val, '[^#]+', 1, lines.COLUMN_VALUE)) val
15 FROM data t,
16 TABLE (CAST (MULTISET
17 (SELECT LEVEL FROM dual CONNECT BY LEVEL <= regexp_count(t.attr, '#')+1
18 ) AS sys.odciNumberList ) ) lines
19 )
20 SELECT val INTO var_val FROM t2 WHERE attr = attr_name;
21 RETURN var_val;
22 END;
23 /
Function created.
Let's call the function:
SQL> SELECT get_val_from_attr('Email') FROM dual;
GET_VAL_FROM_ATTR('EMAIL')
--------------------------
0
SQL> SELECT get_val_from_attr('SMS') FROM dual;
GET_VAL_FROM_ATTR('SMS')
------------------------
1

How to split column value in Oracle sql

How to split a column value which is not having space or any other delimiter. I searched the forums but I couldn't able to find the solution for my scenario.
Here is my scenario
ID Column_Value
011 abcdefgh
The result should be
Column_Value1 Column_Value2
abcd efgh
Assuming you just want to split a column down the middle, you can achieve this with a combination of substr and length:
SELECT SUBSTR(column_value, 1, LENGTH(column_value) / 2),
SUBSTR(column_value, LENGTH(column_value) / 2 + 1)
FROM mytable
SQLFiddle
If you want to split the string based on the length and number of characters, then use SUBSTR as follows:
SQL> with data(str) as(
2 select 'abcdefgh' from dual
3 )
4 select substr(str, 1, 4) col1,
5 substr(str, length(substr(str, 1, 4)) +1) col2
6 from data;
COL1 COL2
---- ----
abcd efgh
SQL>
Above, you could change the value of 4 to your desired value. Remember, both are not mutually exclusive.

Count comma separated values of all columns in Oracle SQL

I have already went through a number of questions and I couldn't find what I am exactly looking for.
Suppose I have a table as follows :
Col1 Col2 Col3
1,2,3 2,3,4,5,1 5,6
I need to get a result as follows using a select statement:
Col1 Col2 Col3
1,2,3 2,3,4,5,1 5,6
3 5 2
Note the added third column is the count of comma separated values.
Finding the count for a single column is simple, but this seems difficult if not impossible.
Thanks in advance.
select
col1,
regexp_count(col1, ',') + 1 as col1count,
col2,
regexp_count(col2, ',') + 1 as col2count,
col3,
regexp_count(col3, ',') + 1 as col3count
from t
FIDDLE
FIDDLE2
Per Count the number of elements in a comma separated string in Oracle an easy way to do this is to count the number of commas and then add 1
You just need the result unioned onto your original data. So, do that:
SQL> with the_data (col1, col2, col3) as (
2 select '1,2,3', '2,3,4,5,1', '5,6' from dual
3 )
4 select a.*
5 from the_data a
6 union all
7 select to_char(regexp_count(col1, ',') + 1)
8 , to_char(regexp_count(col2, ',') + 1)
9 , to_char(regexp_count(col3, ',') + 1)
10 from the_data;
COL1 COL2 COL
----- --------- ---
1,2,3 2,3,4,5,1 5,6
3 5 2
You need to convert the result to a character because you're unioning a character to a number, which Oracle will complain about.
It's worth noting that storing data in this manner violates the first normal form. This makes it far more difficult to manipulate and almost impossible to constrain to be correct. It's worth considering normalising your data model to make this, and other queries, simpler.
Finding the count for a single column is simple, but this seems difficult if not impossible.
So you don't to look for each column manually? You want it dynamically.
The design is actually flawed since it violates normalization. But if you are willing to stay with it, then you could do it in PL/SQL using REGEXP_COUNT.
Something like,
SQL> CREATE TABLE t AS
2 SELECT '1,2,3' Col1,
3 '2,3,4,5,1' Col2,
4 '5,6' Col3
5 FROM dual;
Table created.
SQL>
SQL> DECLARE
2 cnt NUMBER;
3 BEGIN
4 FOR i IN
5 (SELECT column_name FROM user_tab_columns WHERE table_name='T'
6 )
7 LOOP
8 EXECUTE IMMEDIATE 'select regexp_count('||i.column_name||', '','') + 1 from t' INTO cnt;
9 dbms_output.put_line(i.column_name||' has cnt ='||cnt);
10 END LOOP;
11 END;
12 /
COL3 has cnt =2
COL2 has cnt =5
COL1 has cnt =3
PL/SQL procedure successfully completed.
SQL>
Probably, there will be an XML solution in SQL itself, without using PL/SQL.
In SQL -
SQL> WITH DATA AS
2 ( SELECT '1,2,3' Col1, '2,3,4,5,1' Col2, '5,6' Col3 FROM dual
3 )
4 SELECT regexp_count(col1, ',') + 1 cnt1,
5 regexp_count(col2, ',') + 1 cnt2,
6 regexp_count(col3, ',') + 1 cnt3
7 FROM t;
CNT1 CNT2 CNT3
---------- ---------- ----------
3 5 2
SQL>