Oracle 11g 如何对来自两个不同列的值进行分组
Oracle 11g how I can group values from two different columns
我有一个 oracle VIEW,其中包含两列中的一些值我想将这些值分组到一个新列中添加连接:
myView :
---------------------------------
ID | col 1 | col 2 |
---------------------------------
1 | 1,2,3,4 |V1,V2,V3,V4
2 | 4,5,6,7 |V5,V6,V7,V8
我想创建新视图,添加一个新列 col 3,如下所示:
------------------------------------------------------
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
提前感谢您的帮助
您需要将以逗号分隔的 col1
和 col2
值拆分成行,然后连接每一行,然后通过逗号分隔的字符串将连接汇总回一个。
拆分是使用众所周知的技巧完成的,即使用 CONNECT BY
为列表中的每个条目生成一个 "dummy" 行,然后使用 REGEXP_SUBSTR
挑选出每个逗号分隔的行值。
最后卷起是通过 LISTAGG
完成的。
这里是全部(加上额外的测试数据来说明每列中元素数量的不匹配):
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;
结果:
+----+---------+-------------+---------------------+
| 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 |
+----+---------+-------------+---------------------+
您需要在 PLSQL(或更大的 sql 查询)中为每一行执行:
- 按分隔符拆分col1并保存到数组
- 用分隔符拆分col2并保存到数组
- for i=0 解析数组并连接成字符串array1[i] || ',' ||array2[i] || ' '
- 将结果字符串插入col3
拆分方法是这样完成的:
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)
另一种选择是使用自定义 pl/sql 函数。
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;
/
那就这样用吧-
select col1,col2,custom_concat(col1,col2) from temp_123;
结果 -
按照 Matthew 的要求,使用相同的扩展样本数据展开逗号分隔列表的 11gR2 兼容版本:
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
额外的 CTE 将适当的列值转换为单独数字的列表,并针对列表中的每个 ID 和位置连接在一起。正如在 Matthew 的回答中,每个 ID 的所有串联值都聚合成一个 space 分隔的字符串。
但是,回到当前视图的源代码仍然会更简单并且可能更有效 - 假设它本身通过字符串聚合创建 col1
和 col2
值 - 并且将您的新 query/view 基于该原始查询。
在其他视图之上构建视图可能会导致性能问题,因为优化器无法始终将谓词传递到正确的点。但是创建值的聚合列表,将它们拆分,然后重新聚合它们只是做了比您需要的更多的工作。
我有一个 oracle VIEW,其中包含两列中的一些值我想将这些值分组到一个新列中添加连接:
myView :
---------------------------------
ID | col 1 | col 2 |
---------------------------------
1 | 1,2,3,4 |V1,V2,V3,V4
2 | 4,5,6,7 |V5,V6,V7,V8
我想创建新视图,添加一个新列 col 3,如下所示:
------------------------------------------------------
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
提前感谢您的帮助
您需要将以逗号分隔的 col1
和 col2
值拆分成行,然后连接每一行,然后通过逗号分隔的字符串将连接汇总回一个。
拆分是使用众所周知的技巧完成的,即使用 CONNECT BY
为列表中的每个条目生成一个 "dummy" 行,然后使用 REGEXP_SUBSTR
挑选出每个逗号分隔的行值。
最后卷起是通过 LISTAGG
完成的。
这里是全部(加上额外的测试数据来说明每列中元素数量的不匹配):
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;
结果:
+----+---------+-------------+---------------------+
| 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 |
+----+---------+-------------+---------------------+
您需要在 PLSQL(或更大的 sql 查询)中为每一行执行:
- 按分隔符拆分col1并保存到数组
- 用分隔符拆分col2并保存到数组
- for i=0 解析数组并连接成字符串array1[i] || ',' ||array2[i] || ' '
- 将结果字符串插入col3
拆分方法是这样完成的:
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)
另一种选择是使用自定义 pl/sql 函数。
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;
/
那就这样用吧-
select col1,col2,custom_concat(col1,col2) from temp_123;
结果 -
按照 Matthew 的要求,使用相同的扩展样本数据展开逗号分隔列表的 11gR2 兼容版本:
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
额外的 CTE 将适当的列值转换为单独数字的列表,并针对列表中的每个 ID 和位置连接在一起。正如在 Matthew 的回答中,每个 ID 的所有串联值都聚合成一个 space 分隔的字符串。
但是,回到当前视图的源代码仍然会更简单并且可能更有效 - 假设它本身通过字符串聚合创建 col1
和 col2
值 - 并且将您的新 query/view 基于该原始查询。
在其他视图之上构建视图可能会导致性能问题,因为优化器无法始终将谓词传递到正确的点。但是创建值的聚合列表,将它们拆分,然后重新聚合它们只是做了比您需要的更多的工作。