【问题标题】:Oracle 11g how I can group values from two different columnsOracle 11g 如何对来自两个不同列的值进行分组
【发布时间】:2019-01-15 19:58:39
【问题描述】:

我有一个 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

提前感谢您的帮助

【问题讨论】:

  • 希望您没有将值存储为分隔字符串,并且您现有的视图正在执行字符串聚合(即listagg())来生成这些值。如果是这样,返回源表并根据原始值添加另一个聚合列会更简单。否则,您必须将字符串分解为值并将它们重新缝合在一起,这是可能的,但比您需要做的工作更多。

标签: sql oracle plsql sql-view


【解决方案1】:

按照 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 的所有连接值都聚合成一个以空格分隔的字符串。


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

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

【讨论】:

  • 感谢@Alex Pole 这个解决方案解决了这个问题,是的,你是对的,在其他视图之上构建视图可能会导致性能问题,但有时我们会处理大量使用的现有视图更多代码段任何修改都可能导致其他部分代码出现问题和回归,所以我很容易创建一个使用已有视图的视图,谢谢
  • 我不是建议你改变你现有的观点;只是您使用原始源作为起点创建新视图 - 将额外的聚合列添加到其选择列表中。
【解决方案2】:

另一种选择是使用自定义 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;

结果 -

【讨论】:

    【解决方案3】:

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

    使用众所周知的技巧来完成拆分,即使用CONNECT BY 为列表中的每个条目生成一个“虚拟”行,然后使用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               |
    +----+---------+-------------+---------------------+
    

    【讨论】:

    • 无法执行此代码我收到错误缺少关键字
    • 最大的函数没有定义?
    • @ErrabiAyoub - listagg() 在 11gR2 中添加; cross apply 在 12cR1 中添加。但是greatest() 是旧的。
    • 如果您还没有使用 12c,CROSS APPLY 将不适合您。我现在没有时间写一个 11gR2 版本......也许今晚,除非其他人(希望)打败我。
    【解决方案4】:

    您需要在 PLSQL(或更大的 sql 查询)中为每一行执行:

    1. 按分隔符拆分 col1 并保存到数组中
    2. 用分隔符分割 col2 并保存到数组中
    3. 对于 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)
    

    【讨论】:

    • 为什么不呢?获取 2 个相同大小的数组并同时遍历它们以获取值并连接到“每行字符串”,它将为每一行提供所需的结果。只有当数组的长度不同时,它才会起作用。
    • 阅读我的答案,1)拆分 COL1 ; 2)拆分COL2;并同时循环通过 2 个不同的拆分。
    • 您也可以对两个拆分结果进行完全外连接,而不是求助于 PL/SQL。
    猜你喜欢
    • 1970-01-01
    • 2011-03-12
    • 2012-05-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多