Oracle 11g how I can group values from two different columns

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP



Oracle 11g how I can group values from two different columns



I have an oracle VIEW that contains some values inside two columns I want to group those values into a new column adding concatination:


myView :

---------------------------------
ID | col 1 | col 2 |
---------------------------------
1 | 1,2,3,4 |V1,V2,V3,V4
2 | 4,5,6,7 |V5,V6,V7,V8



I want to create new view adding a new column col 3 like this :


------------------------------------------------------
ID | col 1 | col 2 |col 3
------------------------------------------------------
1 | 1,2,3,4 |V1,V2,V3,V4 |1,V1 2,V2 3,V3 4,V4
2 | 5,6,7,8 |V5,V6,V7,V8 |5,V5 6,V6 7,V7 8,V8



Thanks in advance for any help





Hopefully you don't have values stored as delimited strings and your existing view is doing string aggregation (i.e. listagg()) to generate those. If so it would be simpler to go back to the source tables and work from the original values to add another aggregated column. Otherwise you have to explode the string to values and stitch them back together again, which is possible, but more work than you need to do.
– Alex Poole
Aug 8 at 14:11



listagg()




4 Answers
4



As sort of requested by Matthew, an 11gR2-compatible version of exploding the comma-separated lists, using the same extended sample data:


with input_data ( id, col1, col2 ) as (
SELECT 1 , '1,2,3,4', 'V1,V2,V3,V4' from dual union all
SELECT 2 , '4,5,6,7', 'V5,V6,V7,V8' from dual union all
SELECT 3 , 'A', 'VA,VB,VC,VD' from dual union all
SELECT 4 , 'E,F,G', 'VE' from dual union all
SELECT 5 , 'H,I', '' from dual union all
SELECT 6 , '', 'J,K' from dual
)
, cte (id, col1, col2, pos, combined_value) as (
select id, col1, col2, level,
regexp_substr(col1, '(.*?)(,|$)', 1, level, null, 1)
||','|| regexp_substr(col2, '(.*?)(,|$)', 1, level, null, 1)
from input_data
connect by id = prior id
and prior dbms_random.value is not null
and level <= greatest(nvl(regexp_count(col1, ','), 0),
nvl(regexp_count(col2, ','), 0)) + 1
)
select id,
col1,
col2,
listagg(combined_value, ' ') within group (order by pos) as col3
from cte
group by id, col1, col2;

ID COL1 COL2 COL3
---------- ------- ----------- ------------------------------
1 1,2,3,4 V1,V2,V3,V4 1,V1 2,V2 3,V3 4,V4
2 4,5,6,7 V5,V6,V7,V8 4,V5 5,V6 6,V7 7,V8
3 A VA,VB,VC,VD A,VA ,VB ,VC ,VD
4 E,F,G VE E,VE F, G,
5 H,I H, I,
6 J,K ,J ,K



The additional CTE convert the appropriate column values into a list of separate numbers, concatenated together for each ID and position with in the list. And as in Matthew's answer the all the concatenated values for each ID are aggregated into a space-separated string.



However, it would still be simpler and probably more efficient to go back to the source of the current view - assuming that is itself creating the col1 and col2 values through string aggregation - and base your new query/view on that original query.


col1


col2



Building views on top of other views can cause performance issues as the optimiser can't always pass predicates up to the right point. But creating aggregated lists of values, splitting them up, then re-aggregating them is just doing more work than you need.





Thanks @Alex Pole this solution solve the problem ,yes you're right about building views on top of other views can cause performance issues but sometime we work on existing view that are used a lot of time in more pieces of code any modification can caused problem and regression in other part o code so i was simple for me to create a view that used an already view ,Thanks
– Errabi Ayoub
Aug 8 at 15:48





I'm not suggesting you change your existing view; just that you create your new view using the source of the original as a starting point - adding the additional aggregated column to its select list.
– Alex Poole
Aug 8 at 15:51





You got my hint!!! ) Thanks, Alex!
– Matthew McPeak
Aug 8 at 17:17



Another option would be using custom pl/sql function.


CREATE OR REPLACE FUNCTION "STR_TO_TABLE"
(in_strt in varchar2,
in_delim in varchar2 default ',')
return str_table
as
l_str clob default in_strt || in_delim;
l_n number;
l_data str_table := str_table();
begin
loop
l_n := instr( l_str, in_delim );
exit when (nvl(l_n,0) = 0);
l_data.extend;
l_data( l_data.count ) := ltrim(rtrim(substr(l_str,1,l_n-1)));
l_str := substr( l_str, l_n+length(in_delim) );
end loop;
return l_data;
end;

/

create or replace function custom_concat(col1 varchar2,col2 varchar2)
return varchar2
is
conct_val varchar2(4000);
begin
select listagg(final, ' ') within group (order by 1) into conct_val from (
select r1,M.column_value mcv,s.column_value scv,s.s1,M.column_value||','||s.column_value final from (select rownum r1,column_value from table(STR_TO_TABLE(col1))) M join (select rownum s1,column_value from table(STR_TO_TABLE(col2))s1) s on (r1=s1) );
return conct_val;
end;
/



Then use it like this -


select col1,col2,custom_concat(col1,col2) from temp_123;



Result -



result



You need to split the comma-separated col1 and col2 values into rows, then concatenated each row, and then roll up the concatenations back into one by comma-separated string.


col1


col2



Splitting is done using the well-known trick of using CONNECT BY to generate one "dummy" row per entry in the list and then using REGEXP_SUBSTR to pick out each comma-separated value.


CONNECT BY


REGEXP_SUBSTR



Rolling up at the end is done via LISTAGG.


LISTAGG



Here it is all together (with extra test data to account for mismatches in the number of elements in each column):


with input_data ( id, col1, col2 ) as (
SELECT 1 , '1,2,3,4', 'V1,V2,V3,V4' from dual union all
SELECT 2 , '4,5,6,7', 'V5,V6,V7,V8' from dual union all
SELECT 3 , 'A', 'VA,VB,VC,VD' from dual union all
SELECT 4 , 'E,F,G', 'VE' from dual union all
SELECT 5 , 'H,I', '' from dual union all
SELECT 6 , '', 'J,K' from dual
)
select i.id,
i.col1,
i.col2,
listagg(trim(regexp_substr(i.col1, '[^,]+', 1, p.pos)) ||
',' || trim(regexp_substr(i.col2, '[^,]+', 1, p.pos)),',')
within group ( order by p.pos ) col3
from input_data i
cross apply ( select rownum pos
FROM dual
connect by level <=
greatest(nvl(regexp_count(i.col1,','),0),
nvl(regexp_count(i.col2,','),0)) +1 ) p
group by i.id, i.col1, i.col2;



Results:


+----+---------+-------------+---------------------+
| ID | COL1 | COL2 | COL3 |
+----+---------+-------------+---------------------+
| 1 | 1,2,3,4 | V1,V2,V3,V4 | 1,V1,2,V2,3,V3,4,V4 |
| 2 | 4,5,6,7 | V5,V6,V7,V8 | 4,V5,5,V6,6,V7,7,V8 |
| 3 | A | VA,VB,VC,VD | A,VA,,VB,,VC,,VD |
| 4 | E,F,G | VE | E,VE,F,,G, |
| 5 | H,I | | H,,I, |
| 6 | | J,K | ,J,,K |
+----+---------+-------------+---------------------+





could not execute this code I get an error missing keyword
– Errabi Ayoub
Aug 8 at 14:34





greatest function is not defined ?
– Errabi Ayoub
Aug 8 at 14:35





@ErrabiAyoub - listagg() was added in 11gR2; cross apply was added in 12cR1. But greatest() is old.
– Alex Poole
Aug 8 at 14:35



listagg()


cross apply


greatest()





I'm working with oracle 11g
– Errabi Ayoub
Aug 8 at 14:36





If you are not on 12c yet, CROSS APPLY won't work for you. I don't have time right now to write an 11gR2 version... maybe tonight unless someone else (hopefully) beats me to it.
– Matthew McPeak
Aug 8 at 14:43


CROSS APPLY



You need to do in PLSQL (or a bigger sql query) for each row:



This is how the splitting method done:


SELECT num_value
FROM (SELECT TRIM (REGEXP_SUBSTR (num_csv, '[^,]+', 1, LEVEL)) num_value
FROM ( SELECT col1 num_csv FROM table_view)
CONNECT BY LEVEL <= regexp_count (num_csv, ',', 1) + 1)





This code will not give the correct results !
– Errabi Ayoub
Aug 8 at 14:45





Why not ? Getting 2 arrays of same size and looping trough them simoultaniosly to get values and concat to a "per line string", it will give the required result for each row. It will not work only if the arrays are of different length.
– Simion
Aug 8 at 14:48






You're working only with one column col 1 ???
– Errabi Ayoub
Aug 8 at 14:50





Read my answer, 1) split COL1 ; 2) split COL2; and loop trough 2 different splits simultaneously.
– Simion
Aug 8 at 14:51





You could also do a full outer join of the two split results, instead of resorting to PL/SQL.
– Alex Poole
Aug 8 at 15:25






By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

Firebase Auth - with Email and Password - Check user already registered

Dynamically update html content plain JS

How to determine optimal route across keyboard