Count sequential matching words in two strings oracle - sql

I want a query that returns the number of sequential match of words in two strings
example:
Table
Id column1 column2 result
1 'foo bar live' 'foo bar' 2
2 'foo live tele' 'foo tele' 1
3 'bar foo live' 'foo bar live' 0
to get total number of occurrence I am using:
select id, column1,column2,
extractvalue(dbms_xmlgen.getxmltype('select cardinality (
sys.dbms_debug_vc2coll(''' || replace(lower(column1), ' ', ''',''' ) || ''') multiset intersect
sys.dbms_debug_vc2coll('''||replace(lower(column2), ' ', ''',''' )||''')) x from dual'), '//text()') cnt
from table.
Can anyone please suggest a query on similar lines for sequential match also as I want number of sequential matches and number of occurrences shown together.

Personally, in this situation, I would choose PL/SQL code over plain SQL. Something like:
Package specification:
create or replace package PKG is
function NumOfSeqWords(
p_str1 in varchar2,
p_str2 in varchar2
) return number;
end;
Package body:
create or replace package body PKG is
function NumOfSeqWords(
p_str1 in varchar2,
p_str2 in varchar2
) return number
is
l_str1 varchar2(4000) := p_str1;
l_str2 varchar2(4000) := p_str2;
l_res number default 0;
l_del_pos1 number;
l_del_pos2 number;
l_word1 varchar2(1000);
l_word2 varchar2(1000);
begin
loop
l_del_pos1 := instr(l_str1, ' ');
l_del_pos2 := instr(l_str2, ' ');
case l_del_pos1
when 0
then l_word1 := l_str1;
l_str1 := '';
else l_word1 := substr(l_str1, 1, l_del_pos1 - 1);
end case;
case l_del_pos2
when 0
then l_word2 := l_str2;
l_str2 := '';
else l_word2 := substr(l_str2, 1, l_del_pos2 - 1);
end case;
exit when (l_word1 <> l_word2) or
((l_word1 is null) or (l_word2 is null));
l_res := l_res + 1;
l_str1 := substr(l_str1, l_del_pos1 + 1);
l_str2 := substr(l_str2, l_del_pos2 + 1);
end loop;
return l_res;
end;
end;
Test case:
with t1(Id1, col1, col2) as(
select 1, 'foo bar live' ,'foo bar' from dual union all
select 2, 'foo live tele' ,'foo tele' from dual union all
select 3, 'bar foo live' ,'foo bar live'from dual
)
select id1
, col1
, col2
, pkg.NumOfSeqWords(col1, col2) as res
from t1
;
Result:
ID1 COL1 COL2 RES
---------- ------------- ------------ ----------
1 foo bar live foo bar 2
2 foo live tele foo tele 1
3 bar foo live foo bar live 0

Why to give up on the query approach. I know it's a bit complicated and I hope someone can work on it to improve it, but working on this during my spare time I was able to survive a an afternoon of calls...
Here on SQLFidlle
SELECT Table1.id,
Table1.column1,
Table1.column2,
max(nvl(t.l,0)) RESULT
FROM (
SELECT id,
column1,
column2,
LEVEL l,
decode(LEVEL,
1,
substr(column1, 1, instr(column1,' ', 1, LEVEL) -1),
substr(column1, 1, (instr(column1,' ', 1, LEVEL )))
) sub1,
decode(LEVEL,
1,
substr(column2, 1, instr(column2,' ', 1, LEVEL) -1),
substr(column2, 1, (instr(column2,' ', 1, LEVEL )))
) sub2
FROM (SELECT id,
column1 || ' ' column1,
column2 || ' ' column2
FROM Table1)
WHERE decode(LEVEL,
1,
substr(column1, 1, instr(column1,' ', 1, LEVEL) -1),
substr(column1, 1, (instr(column1,' ', 1, LEVEL )))
) =
decode(LEVEL,
1,
substr(column2, 1, instr(column2,' ', 1, LEVEL) -1),
substr(column2, 1, (instr(column2,' ', 1, LEVEL )))
)
START WITH column1 IS NOT NULL
CONNECT BY instr(column1,' ', 1, LEVEL) > 0
) t
RIGHT OUTER JOIN Table1 ON trim(t.column1) = Table1.column1
AND trim(t.column2) = Table1.column2
AND t.id = Table1.id
GROUP BY Table1.id,
Table1.column1,
Table1.column2
ORDER BY max(nvl(t.l,0)) DESC

I know this issue is old but I found a good solution:
You can test from here https://rextester.com/l/oracle_online_compiler
select
id1,
col1,
col2,
(
Select Count(*)
From
(Select Upper(To_Char(Trim(Substr(Column_Value,0,Length(Column_Value))))) w1
From xmltable(('"' || Replace(Replace(col1,' ', ','), ',', '","') || '"'))
Where Upper(To_Char(Trim(Substr(Column_Value,0,Length(Column_Value))))) Is Not Null) c1,
(Select Upper(To_Char(Trim(Substr(Column_Value,0,Length(Column_Value))))) w2
From xmltable(('"' || Replace(Replace(col2,' ', ','), ',', '","') || '"'))
Where Upper(To_Char(Trim(Substr(Column_Value,0,Length(Column_Value))))) Is Not Null) c2
Where c1.w1 = c2.w2
) Test
From
(select 1 Id1, 'foo bar live' col1, 'foo bar' col2 from dual union all
select 2, 'foo live tele pepe gato coche' ,'bar foo live tele perro gato' from dual union all
select 3, 'bar foo live tele perro gato' ,'foo bar live'from dual) t1

Related

SQL Oracle object%rowtype dynamic

I have an object which is a ROWTYPE from a table and need to recover some of the columns to an array list.
For example:
The ROWTYPE contents Name1, Last_Name1, Id1, Adress1...Name25,Last_Name25,Id25,Adress25 in columns.
I need to know if I can recover them by a dynamic way with a loop to an array type like this:
Name(1-25),Last_Name(1-25),Id(1-25),Adress(1-25).
Given a simplified example of the problem with the table:
CREATE TABLE table_name (id1, name1, id2, name2, id3, name3, id4, name4) AS
SELECT 1, 'N1', 2, 'N2', 3, 'N3', 4, 'N4' FROM DUAL;
I am assuming, that to get the %ROWTYPE variable, you are using something like:
DECLARE
data TABLE_NAME%ROWTYPE;
BEGIN
SELECT * INTO data FROM table_name FETCH FIRST ROW ONLY;
DBMS_OUTPUT.PUT_LINE('1: ' || data.id1 || ', ' || data.name1);
DBMS_OUTPUT.PUT_LINE('2: ' || data.id2 || ', ' || data.name2);
DBMS_OUTPUT.PUT_LINE('3: ' || data.id3 || ', ' || data.name3);
DBMS_OUTPUT.PUT_LINE('4: ' || data.id4 || ', ' || data.name4);
END;
/
Rather than trying to dynamically access the %ROWTYPE record, why do you not bypass the problem and separate the values in the SELECT statement using UNPIVOT in a cursor and then you can use %ROWTYPE on the cursor (rather than the table):
DECLARE
CURSOR cur IS
SELECT id, name
FROM (SELECT * FROM table_name FETCH FIRST ROW ONLY)
UNPIVOT (
(id, name)
FOR idx IN (
(id1, name1) AS 1,
(id2, name2) AS 2,
(id3, name3) AS 3,
(id4, name4) AS 4
)
);
TYPE cur_row_arr IS TABLE OF cur%ROWTYPE;
rw cur%ROWTYPE;
arr cur_row_arr := cur_row_arr();
BEGIN
OPEN cur;
LOOP
FETCH cur INTO rw;
EXIT WHEN cur%NOTFOUND;
arr.EXTEND;
arr(arr.COUNT) := rw;
END LOOP;
CLOSE cur;
FOR i IN 1 .. arr.COUNT LOOP
DBMS_OUTPUT.PUT_LINE( i || ': ' || arr(i).id || ', ' || arr(i).name );
END LOOP;
END;
/
db<>fiddle here
Don't try to do it dynamically, just hard-code the values.
Given the simplified example:
CREATE TABLE table_name (id1, name1, id2, name2, id3, name3, id4, name4) AS
SELECT 1, 'N1', 2, 'N2', 3, 'N3', 4, 'N4' FROM DUAL;
Then:
CREATE PROCEDURE test(
v_row IN TABLE_NAME%ROWTYPE
)
IS
TYPE details_type IS RECORD(
id TABLE_NAME.ID1%TYPE,
name TABLE_NAME.NAME1%TYPE
);
TYPE details_tab_type IS TABLE OF details_type;
details details_tab_type := details_tab_type();
BEGIN
details.EXTEND(4);
details(1) := details_type(v_row.id1, v_row.name1);
details(2) := details_type(v_row.id2, v_row.name2);
details(3) := details_type(v_row.id3, v_row.name3);
details(4) := details_type(v_row.id4, v_row.name4);
FOR i IN 1 .. details.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(i || ': ' || details(i).id || ', ' || details(i).name);
END LOOP;
END;
/
All you need to do is write the first assignment statement details(1) := details_type(v_row.id1, v_row.name1); and then copy-paste multiple copies and increment the numbers by 1 each time.
Then you can call it using:
DECLARE
v_row TABLE_NAME%ROWTYPE;
BEGIN
SELECT * INTO v_row FROM table_name FETCH FIRST ROW ONLY;
test(v_row);
END;
/
Which outputs:
1: 1, N1
2: 2, N2
3: 3, N3
4: 4, N4
db<>fiddle here

How to take where clause conditions from table column in oracle SQL or plsql

How to take where clause conditions from table column in oracle plsql.
E.g. data in table
Condition
1.sourceSystemId = 'SN'
2.AND(coverageType='AD',amountType1='PREMIUM',premiumFrequency='REGULAR',yearOfPremium='1')
e.g query:
select * from xyz where rule='abc' and "sourceSystemId = 'SN'"
select * from xyz where rule='abc' AND(coverageType='AD',amountType1='PREMIUM',premiumFrequency='REGULAR',yearOfPremium='1')
Not entirely sure what you're asking here, but I would imagine that
select * from xyz where rule='abc' AND(coverageType='AD',amountType1='PREMIUM',premiumFrequency='REGULAR',yearOfPremium='1')
would become
select * from xyz
where rule='abc'
AND coverageType='AD'
and amountType1='PREMIUM'
and premiumFrequency='REGULAR'
and yearOfPremium='1'
I suppose you want something like :
DECLARE
l_query VARCHAR2(2000) := 'select * from xyz where rule=''abc''';
l_result xyz%ROWTYPE;
l_cursor SYS_REFCURSOR;
BEGIN
dbms_output.put_line(l_query);
FOR clause IN (SELECT condition
FROM conditions)
LOOP
l_query := l_query||' AND '||clause.condition;
END LOOP;
OPEN l_cursor FOR l_query;
LOOP
FETCH l_cursor INTO l_result;
EXIT WHEN l_cursor%NOTFOUND;
..
-- your processing
END LOOP;
CLOSE l_cursor;
END;
Here is example of SQL solution. I used justt first and last condition but you can get them all...
WITH
xyz As
(
Select 1 "ID", 'abc' "RULE", 'AD' "COVERAGETYPE", 'PREMIUM' "AMOUNTTYPE1", 'REGULAR' "PREMIUMFREQUENCY", '1' "YEAROFPREMIUM" From Dual
UNION
Select 2 "ID", 'abc' "RULE", 'BF' "COVERAGETYPE", 'ORDINARY' "AMOUNTTYPE1", 'EXTRA' "PREMIUMFREQUENCY", '2' "YEAROFPREMIUM" From Dual
UNION
Select 3 "ID", 'abc' "RULE", 'AD' "COVERAGETYPE", 'PREMIUM' "AMOUNTTYPE1", 'REGULAR' "PREMIUMFREQUENCY", '1' "YEAROFPREMIUM" From Dual
),
conditions As
(
SELECT UPPER('coverageType=AD,amountType1=PREMIUM,premiumFrequency=REGULAR,yearOfPremium=1') "CND" From Dual
)
SELECT
x.ID, x.RULE, x.COVERAGETYPE, x.AMOUNTTYPE1, x.PREMIUMFREQUENCY, x.YEAROFPREMIUM
FROM
xyz x
INNER JOIN
conditions c ON(1=1)
WHERE
x.RULE = 'abc' And
x.COVERAGETYPE = CASE WHEN InStr(c.CND || ',', 'COVERAGETYPE=') = 0 THEN x.COVERAGETYPE
ELSE SubStr(SubStr(c.CND || ',', InStr(c.CND || ',', 'COVERAGETYPE=') + Length('COVERAGETYPE=')), 1, InStr(SubStr(c.CND || ',', InStr(c.CND || ',', 'COVERAGETYPE=') + Length('COVERAGETYPE=') + 1), ',')) END And
x.YEAROFPREMIUM = CASE WHEN InStr(c.CND || ',', 'YEAROFPREMIUM=') = 0 THEN x.YEAROFPREMIUM
ELSE SubStr(SubStr(c.CND || ',', InStr(c.CND || ',', 'YEAROFPREMIUM=') + Length('YEAROFPREMIUM=')), 1, InStr(SubStr(c.CND || ',', InStr(c.CND || ',', 'YEAROFPREMIUM=') + Length('YEAROFPREMIUM=') + 1), ',')) END
Result:
ID RULE COVERAGETYPE AMOUNTTYPE1 PREMIUMFREQUENCY YEAROFPREMIUM
1 abc AD PREMIUM REGULAR 1
3 abc AD PREMIUM REGULAR 1

pl sql query to compare 2 columns comma separated values and find differences

COL 1 Values: QQQ,QQ,123,VVVV
COL 2 VALUES: WWWW,VVV,QQQ
Compare COL1 values vs COL 2 values:
For e.g.
1) If a value exist in COL1 but NOT in COL2 then display that COL1 value under COL VALUE ADDED
Output Expected: COL VALUE ADDED = QQ,123,VVVV
2) If a value exist in COL2 but NOT in COL1 then display that COL2 values under COL VALUE Removed.
Output Expected: COL VALUE REMOVED = WWWW,VVV
3) If the set of values are same within COL1 and COL2 then display it as NULL
Can these be handled and compared as expected dynamically via PLSQL command? I expect to have dynamic comma separated values that needs to be compared between Current and Before set of values.
Here's one option:
SQL> with
2 test (col1, col2) as
3 (select 'QQQ,QQ,123,VVVV', 'WWWW,VVV,QQQ' from dual
4 ),
5 t1 (col) as
6 (select regexp_substr(col1, '[^,]+', 1, level)
7 from test
8 connect by level <= regexp_count(col1, ',') + 1
9 ),
10 t2 (col) as
11 (select regexp_substr(col2, '[^,]+', 1, level)
12 from test
13 connect by level <= regexp_count(col2, ',') + 1
14 ),
15 one_minus_two as
16 (select col from t1
17 minus
18 select col from t2
19 ),
20 two_minus_one as
21 (select col from t2
22 minus
23 select col from t1
24 )
25 select 'Col value added: ' ||
26 listagg(col, ',') within group (order by null) as result
27 from one_minus_two
28 union all
29 select 'Col value removed: ' ||
30 listagg(col, ',') within group (order by null)
31 from two_minus_one
32 union all
33 select 'NULL'
34 from dual
35 where (select listagg(col, ',') within group (order by col) from t1) =
36 (select listagg(col, ',') within group (order by col) from t2);
RESULT
--------------------------------------------------------------------------------
Col value added: 123,QQ,VVVV
Col value removed: VVV,WWWW
SQL>
If both values are equal:
SQL> with
2 test (col1, col2) as
3 (select 'AAA,BBB,CCC', 'CCC,AAA,BBB' from dual
4 ),
<SNIP>
35 where (select listagg(col, ',') within group (order by col) from t1) =
36 (select listagg(col, ',') within group (order by col) from t2);
RESULT
--------------------------------------------------------------------------------
Col value added:
Col value removed:
NULL
SQL>
Here's one way to solve it:
WITH cteData AS (SELECT 'QQQ,QQ,123,VVVV' AS COL1,
'WWWW,VVV,QQQ' AS COL2
FROM DUAL),
cteCol1 AS (SELECT REGEXP_SUBSTR(COL1, '[^,]+', 1, LEVEL) AS COL1
FROM cteData
CONNECT BY LEVEL < REGEXP_COUNT(COL1, ',')+2),
cteCol2 AS (SELECT REGEXP_SUBSTR(COL2, '[^,]+', 1, LEVEL) AS COL2
FROM cteData
CONNECT BY LEVEL < REGEXP_COUNT(COL2, ',')+2),
cteAdded AS (SELECT c1.COL1 -- COL1 not found in COL2
FROM cteCol1 c1
WHERE c1.COL1 NOT IN (SELECT COL2
FROM cteCol2)),
cteRemoved AS (SELECT c2.COL2 -- in COL2 but NOT in COL1
FROM cteCol2 c2
WHERE c2.COL2 NOT IN (SELECT COL1
FROM cteCol1)),
cteAdded_list AS (SELECT LISTAGG(a.COL1, ',') WITHIN GROUP (ORDER BY 1) AS ADDED
FROM cteAdded a),
cteRemoved_list AS (SELECT LISTAGG(r.COL2, ',') WITHIN GROUP (ORDER BY 1) AS REMOVED
FROM cteRemoved r),
cteResults AS (SELECT CASE
WHEN a.ADDED IS NULL THEN NULL
ELSE 'COL VALUE ADDED = ' || a.ADDED
END AS RESULT
FROM cteAdded_list a
UNION ALL
SELECT CASE
WHEN r.REMOVED IS NULL THEN NULL
ELSE 'COL VALUE REMOVED = ' || r.REMOVED
END AS RESULT
FROM cteRemoved_list r)
SELECT RESULT
FROM cteResults
WHERE RESULT IS NOT NULL
With the data you supplied this returns:
COL VALUE ADDED = 123,QQ,VVVV
COL VALUE REMOVED = VVV,WWWW
If you replace the strings in cteData with ones which are identical, e.g. set both to '123,456,789' then it returns an empty result set.
Since you wanted a PL/SQL solution, you can create a function to split the string to an array and then use MULTISET operators:
Split Function:
From my previous answer
CREATE OR REPLACE FUNCTION split_String(
i_str IN VARCHAR2,
i_delim IN VARCHAR2 DEFAULT ','
) RETURN stringlist DETERMINISTIC
AS
p_result stringlist := stringlist();
p_start NUMBER(5) := 1;
p_end NUMBER(5);
c_len CONSTANT NUMBER(5) := LENGTH( i_str );
c_ld CONSTANT NUMBER(5) := LENGTH( i_delim );
BEGIN
IF c_len > 0 THEN
p_end := INSTR( i_str, i_delim, p_start );
WHILE p_end > 0 LOOP
p_result.EXTEND;
p_result( p_result.COUNT ) := SUBSTR( i_str, p_start, p_end - p_start );
p_start := p_end + c_ld;
p_end := INSTR( i_str, i_delim, p_start );
END LOOP;
IF p_start <= c_len + 1 THEN
p_result.EXTEND;
p_result( p_result.COUNT ) := SUBSTR( i_str, p_start, c_len - p_start + 1 );
END IF;
END IF;
RETURN p_result;
END;
/
PL/SQL:
Then you can use it in a PL/SQL block:
DECLARE
col1 VARCHAR2(4000) := 'QQQ,QQ,123,VVVV';
col2 VARCHAR2(4000) := 'WWWW,VVV,QQQ';
arr1 stringlist := SPLIT_STRING( col1 );
arr2 stringlist := SPLIT_STRING( col2 );
added stringlist := arr1 MULTISET EXCEPT arr2;
removed stringlist := arr2 MULTISET EXCEPT arr1;
BEGIN
FOR i IN 1 .. added.COUNT LOOP
DBMS_OUTPUT.PUT( added(i) || ',' );
END LOOP;
DBMS_OUTPUT.NEW_LINE();
FOR i IN 1 .. removed.COUNT LOOP
DBMS_OUTPUT.PUT( removed(i) || ',' );
END LOOP;
DBMS_OUTPUT.NEW_LINE();
END;
/
Which outputs:
dbms_output:
QQ,123,VVVV,
WWWW,VVV,
SQL:
If you want to implement it in SQL then with the test data:
CREATE TABLE test_data ( col1, col2 ) AS
SELECT 'QQQ,QQ,123,VVVV', 'WWWW,VVV,QQQ' FROM DUAL;
You can query it using:
SELECT ( SELECT LISTAGG( column_value, ',' ) WITHIN GROUP ( ORDER BY ROWNUM )
FROM TABLE( a.arr1 MULTISET EXCEPT a.arr2 ) ) AS added,
( SELECT LISTAGG( column_value, ',' ) WITHIN GROUP ( ORDER BY ROWNUM )
FROM TABLE( a.arr2 MULTISET EXCEPT a.arr1 ) ) AS removed
FROM (
SELECT SPLIT_STRING( col1 ) AS arr1,
SPLIT_STRING( col2 ) AS arr2
FROM test_data
) a;
Which outputs:
ADDED | REMOVED
:---------- | :-------
QQ,123,VVVV | WWWW,VVV
db<>fiddle here
db<>fiddle here

SQL Concatenate strings across multiple columns with corresponding values

I'm looking for a way to achieve this in a SELECT statement.
FROM
Column1 Column2 Column3
A,B,C 1,2,3 x,y,z
TO
Result
A|1|x,B|2|y,C|3|z
The delimiters don't matter. I'm just trying to to get all the data in one single column. Ideally I am looking to do this in DB2. But I'd like to know if there's an easier way to get this done in Oracle.
Thanks
You can do it like this using INSTR and SUBSTR:
select
substr(column1,1,instr(column1,',',1)-1) || '|' ||
substr(column2,1,instr(column2,',',1)-1) || '|' ||
substr(column3,1,instr(column3,',',1)-1) || '|' ||
',' ||
substr(column1 ,instr(column1 ,',',1,1)+1,instr(column1 ,',',1,2) - instr(column1 ,',',1)-1) || '|' ||
substr(column2 ,instr(column2 ,',',1,1)+1,instr(column2 ,',',1,2) - instr(column2 ,',',1)-1) || '|' ||
substr(column3 ,instr(column3 ,',',1,1)+1,instr(column3 ,',',1,2) - instr(column3 ,',',1)-1) || '|' ||
',' ||
substr(column1 ,instr(column1 ,',',1,2)+1) || '|' ||
substr(column2 ,instr(column2 ,',',1,2)+1) || '|' ||
substr(column3 ,instr(column3 ,',',1,2)+1)
from yourtable
i tried some thing. just look into link
first i created a table called t_ask_test and inserted the data based on the above question. Achieved the result by using the string functions
sample table
create table t_ask_test(column1 varchar(10), column2 varchar(10),column3 varchar(10));
inserted a row
insert into T_ASK_TEST values ('A,B,C','1,2,3','x,y,z');
the following query will be in dynamic way
select substr(column1,1,instr(column1,',',1,1)-1)||'|'||substr(column2,1,instr(column1,',',1,1)-1)||'|'||substr(column3,1,instr(column1,',',1,1)-1) ||','||
substr(column1,instr(column1,',',1,1)+1,instr(column1,',',1,2)-instr(column1,',',1,1)-1)||'|'||substr(column2,instr(column2,',',1,1)+1,instr(column2,',',1,2)-instr(column2,',',1,1)-1)||'|'||substr(column3,instr(column3,',',1,1)+1,instr(column3,',',1,2)-instr(column3,',',1,1)-1) ||','||
substr(column1,instr(column1,',',1,2)+1,length(column1)-instr(column1,',',1,2))||'|'||substr(column2,instr(column2,',',1,2)+1,length(column2)-instr(column2,',',1,2))||'|'||substr(column3,instr(column3,',',1,2)+1,length(column3)-instr(column3,',',1,2)) as test from t_ask_test;
output will be as follows
TEST
---------------
A|1|x,B|2|y,C|3|z
If you have a dynamic number of entries for each row then:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE TEST ( Column1, Column2, Column3 ) AS
SELECT 'A,B,C', '1,2,3', 'x,y,z' FROM DUAL
UNION ALL SELECT 'D,E', '4,5', 'v,w' FROM DUAL;
Query 1:
WITH ids AS (
SELECT t.*, ROWNUM AS id
FROM TEST t
)
SELECT LISTAGG(
REGEXP_SUBSTR( i.Column1, '[^,]+', 1, n.COLUMN_VALUE )
|| '|' || REGEXP_SUBSTR( i.Column2, '[^,]+', 1, n.COLUMN_VALUE )
|| '|' || REGEXP_SUBSTR( i.Column3, '[^,]+', 1, n.COLUMN_VALUE )
, ','
) WITHIN GROUP ( ORDER BY n.COLUMN_VALUE ) AS value
FROM ids i,
TABLE(
CAST(
MULTISET(
SELECT LEVEL
FROM DUAL
CONNECT BY LEVEL <= GREATEST(
REGEXP_COUNT( i.COLUMN1, '[^,]+' ),
REGEXP_COUNT( i.COLUMN2, '[^,]+' ),
REGEXP_COUNT( i.COLUMN3, '[^,]+' )
)
)
AS SYS.ODCINUMBERLIST
)
) n
GROUP BY i.ID
Results:
| VALUE |
|-------------------|
| A|1|x,B|2|y,C|3|z |
| D|4|v,E|5|w |
You need to use:
SUBSTR
INSTR
|| concatenation operator
It would be easy if you break your output, and then understand how it works.
SQL> WITH t AS
2 ( SELECT 'A,B,C' Column1, '1,2,3' Column2, 'x,y,z' Column3 FROM dual
3 )
4 SELECT SUBSTR(column1, 1, instr(column1, ',', 1) -1)
5 ||'|'
6 || SUBSTR(column2, 1, instr(column2, ',', 1) -1)
7 ||'|'
8 || SUBSTR(column3, 1, instr(column1, ',', 1) -1)
9 ||','
10 || SUBSTR(column1, instr(column1, ',', 1, 2) +1 - instr(column1, ',', 1),
11 instr(column1, ',', 1) -1)
12 ||'|'
13 || SUBSTR(column2, instr(column2, ',', 1, 2) +1 - instr(column2, ',', 1),
14 instr(column2, ',', 1) -1)
15 ||'|'
16 || SUBSTR(column3, instr(column3, ',', 1, 2) +1 - instr(column3, ',', 1),
17 instr(column3, ',', 1) -1)
18 ||','
19 || SUBSTR(column1, instr(column1, ',', 1, 3) +1 - instr(column1, ',', 1),
20 instr(column1, ',', 2) -1)
21 as "new_column"
22 FROM t;
new_column
-------------
A|1|x,B|2|y,C
On a side note, you should avoid storing delimited values in a single column. Consider normalizing the data.
From Oracle 11g and above, you could create a VIRTUAL COLUMN using the above expression and use it instead of executing the SQL frequently.
Its very simple in oracle. just use the concatenation operatort ||.
In the below solution, I have used underscore as the delimiter
select Column1 ||'_'||Column2||'_'||Column3 from table_name;

How to pick only a particular set of values from a column and generate an update statement based on that

I have a problem where I need to run a set of update statements based on column values.
So we will get a set of IDs like below after a select query.
-------------------------------------
ID ChangedColumns ChangedValues
-------------------------------------
101 col1|col2| [A^B]|[123^456]|
102 col3| [XXX^YYY]|
... .... ....
and so on
Where ChangedColumns are the column names which are changed and ChangedValues will tell the old and new values for the respective columns (delimited by a pipe '|' and enclosed in braces '[]')
Based on this, I need to run update statements on another table, which will be something like
update table
set col1='A',
col2='123'
where id = 101;
update table
set col3='XXX'
where id = 102;
update table
.....
..... and so on
The number of Changed Columns can be 1 - 20 and its corresponding changed values will be in same order in ChangedValues column.
Could someone please let me know how to write a PL/SQL block for the same?
Many thanks
First I broke the data down by ID and ChangedColumn:
SQL> with tbl(ID, ChangedColumns, ChangedValues) as (
select 101, 'col1|col2|', '[A^B]|[123^456]|' from dual
union
select 102, 'col3|', '[XXX^YYY]|' from dual
)
SELECT ID,
REGEXP_SUBSTR(ChangedColumns ,'([^|]*)(\|)', 1, COLUMN_VALUE, NULL, 1 ) as changedcolumn,
REGEXP_SUBSTR(ChangedValues ,'([^|]*)(\|)', 1, COLUMN_VALUE, NULL, 1 ) as column_value
FROM tbl,
TABLE(
CAST(
MULTISET(
SELECT LEVEL
FROM DUAL
CONNECT BY LEVEL <= REGEXP_COUNT( ChangedColumns ,'\|' )
) AS SYS.ODCINUMBERLIST
)
);
ID CHANGEDCOL COLUMN_VALUE
---------- ---------- ----------------
101 col1 [A^B]
101 col2 [123^456]
102 col3 [XXX^YYY]
SQL>
Then changed that to generate a separate update statement for each column that changed for each ID. The next step is to work it into one update statement for each ID, handling all columns that changed:
SQL> with tbl(ID, ChangedColumns, ChangedValues) as (
select 101, 'col1|col2|', '[A^B]|[123^456]|' from dual
union
select 102, 'col3|', '[XXX^YYY]|' from dual
)
SELECT 'update table set ' ||
REGEXP_SUBSTR(ChangedColumns, '([^|]*)(\|)', 1, COLUMN_VALUE, NULL, 1 ) ||
' = ''' ||
REGEXP_SUBSTR(ChangedValues, '\[([^\^]*)(\^)', 1, COLUMN_VALUE, NULL, 1 ) ||
'''' ||
' where id = ' || id || ';' as update_stmt
FROM tbl,
TABLE(
CAST(
MULTISET(
SELECT LEVEL
FROM DUAL
CONNECT BY LEVEL <= REGEXP_COUNT( ChangedColumns ,'\|' )
) AS SYS.ODCINUMBERLIST
)
);
UPDATE_STMT
--------------------------------------------------------------------------------
update table set col1 = 'A' where id = 101;
update table set col2 = '123' where id = 101;
update table set col3 = 'XXX' where id = 102;
SQL>
EDIT: Here's the final product after some further refining and tweaking:
SQL> with tbl_a(ID, ChangedColumns, ChangedValues) as (
select 101, 'col1|col2|', '[A^B]|[123^456]|' from dual
union
select 102, 'col3|', '[XXX^YYY]|' from dual
union
select 103, 'col4|col5|col6|col7|col8|', '[XX0^YY0]|[XX1^YY1]|[XX2^YY2]|[XX3^YY3]|[XX4^YY4]|' from dual
),
tbl_b(ID, column_value) as (
SELECT ID,
REGEXP_SUBSTR(ChangedColumns ,'([^|]*)(\|)', 1, COLUMN_VALUE, NULL, 1 ) ||
' = ' || CHR(39) ||
REGEXP_SUBSTR(ChangedValues ,'\[([^\^]*)(\^)', 1, COLUMN_VALUE, NULL, 1 ) || CHR(39)
FROM tbl_a,
TABLE(
CAST(
MULTISET(
SELECT LEVEL
FROM DUAL
CONNECT BY LEVEL <= REGEXP_COUNT( ChangedColumns ,'\|' )
) AS SYS.ODCINUMBERLIST
)
)
)
SELECT distinct ID,
'UPDATE TABLE SET ' || LISTAGG(column_value, ', ')
WITHIN GROUP (ORDER BY id, column_value)
OVER (PARTITION BY id) || ' WHERE ID = ' || ID || ';' as update_stmt
FROM tbl_b
ORDER BY ID;
ID UPDATE_STMT
--------- ------------------------------------------------------------------------------------------------------
101 UPDATE TABLE SET col1 = 'A', col2 = '123' WHERE ID = 101;
102 UPDATE TABLE SET col3 = 'XXX' WHERE ID = 102;
103 UPDATE TABLE SET col4 = 'XX0', col5 = 'XX1', col6 = 'XX2', col7 = 'XX3', col8 = 'XX4' WHERE ID = 103;
SQL>
I suspect this could be further simplified, but there's something to be said for breaking a problem down into smaller steps.
UPDATE table2
SET table2.col1 = table1.col1,
table2.col2 = table1.col2,
...
FROM table1, table2
WHERE table1.memberid = table2.memberid