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

提前感谢您的帮助

您需要将以逗号分隔的 col1col2 值拆分成行,然后连接每一行,然后通过逗号分隔的字符串将连接汇总回一个。

拆分是使用众所周知的技巧完成的,即使用 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 查询)中为每一行执行:

  1. 按分隔符拆分col1并保存到数组
  2. 用分隔符拆分col2并保存到数组
  3. for i=0 解析数组并连接成字符串array1[i] || ',' ||array2[i] || ' '
  4. 将结果字符串插入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 分隔的字符串。


但是,回到当前视图的源代码仍然会更简单并且可能更有效 - 假设它本身通过字符串聚合创建 col1col2 值 - 并且将您的新 query/view 基于该原始查询。

在其他视图之上构建视图可能会导致性能问题,因为优化器无法始终将谓词传递到正确的点。但是创建值的聚合列表,将它们拆分,然后重新聚合它们只是做了比您需要的更多的工作。