【问题标题】:Deduplicate Oracle xmlagg list去重 Oracle xmlagg 列表
【发布时间】:2020-01-09 09:54:36
【问题描述】:

使用 Oracle 11.2 我正在尝试从下表中选择 2 个分组列表,这是我的代码:

CREATE  TABLE tmp_main AS (
  SELECT rownum col1, 'txt_' || to_char(rownum) Col2 FROM dual 
    CONNECT BY rownum<=2
);

CREATE TABLE tmp_keys AS (
  SELECT DECODE(rownum,1,1,2,1,3,1,4,2,5,2,6,2) col1, 'key_' || to_char(rownum) key1 , rownum seq FROM dual 
     CONNECT BY rownum<=6
);

CREATE TABLE tmp_line AS (
  SELECT DECODE(rownum,1,1,2,1,3,1,4,1,5,2,6,2,7,2,8,2) col1, 'line_' || DECODE(rownum,2,1,3,1,4,2,5,3,7,3,8,4) line1 , rownum seq   FROM dual 
     CONNECT BY rownum<=8
);

update tmp_line set line1=null where line1='line_';
update tmp_keys set seq=null where col1=1;

tmp_keys.seq 可以为空,所以我需要先按 seq 排序,然后按 key1 这是我尝试过的:

SELECT  m.col1,m.col2,
RTRIM(XMLAGG(XMLELEMENT(E,k.key1 , ',').EXTRACT('//text()') ORDER BY k.seq,k.key1 ).GetClobVal(),',') as key_list ,
RTRIM(XMLAGG(XMLELEMENT(E,l.line1 || ',').EXTRACT('//text()') ORDER BY l.seq ).GetClobVal(),',')  line_list
FROM tmp_main m
JOIN tmp_keys k
ON m.col1=k.col1
JOIN tmp_line l
ON m.col1=l.col1
group by m.col1,col2;

这给出了:

col1 col2   key_list                                                                    line_list
1   txt_1   key_1,key_1,key_1,key_1,key_2,key_2,key_2,key_2,key_3,key_3,key_3,key_3     ,,,line_1,line_1,line_1,line_1,line_1,line_1,line_2,line_2,line_2
2   txt_2   key_4,key_4,key_4,key_4,key_5,key_5,key_5,key_5,key_6,key_6,key_6,key_6     line_3,line_3,line_3,,,,line_3,line_3,line_3,line_4,line_4,line_4

即重复。

我想要的是:

col1 col2   key_list                    line_list
1   txt_1   key_1,key_2,key_3           ,line_1,line_1,line_2
2   txt_2   key_3,key_4,key_5           line_3,,line_3,line_4

即保留空 line1 值。

注意事项:

  • 真正的查询要大得多,因此 1 次表扫描会很好,因为速度很重要。
  • 这 2 个列表可能 > 4000 个字符,因此不允许使用 listagg 或类似函数(这就是我使用 xmlagg 和 GetClobVal() 的原因)

任何帮助表示赞赏

【问题讨论】:

    标签: sql oracle


    【解决方案1】:

    首先,为键和行值分配一个row_number,从1 开始为每个键或行seq

    因此,您将partition by 最终分组值和相关序列。然后按键/行值排序:

    row_number() over ( 
      partition by m.col1,col2,k.seq
      order by key1
    ) rnk, 
    row_number() over ( 
      partition by m.col1,col2,l.seq
      order by line1
    ) rnl
    

    然后仅聚合此行号 = 1 的那些行:

    with rws as (
      select 
        m.*, line1, key1,
        l.seq seql,k.seq seqk,
        row_number() over ( 
          partition by m.col1,col2,k.seq
          order by key1
        ) rnk, 
        row_number() over ( 
          partition by m.col1,col2,l.seq
          order by line1
        ) rnl
      from tmp_main m
      join tmp_keys k
      on   m.col1=k.col1
      join tmp_line l
      on   m.col1=l.col1
    )
      select 
        col1,col2,
        rtrim(
          xmlagg(
            xmlelement(
              e,
              case when rnk = 1 then key1 || ',' end 
            ).extract('//text()') 
            order by seqk
          ).getclobval(),','
        ) key_list ,
        rtrim(
          xmlagg(
            xmlelement(
              e,
              case when rnl = 1 then line1 || ',' end 
            ).extract('//text()') 
            order by seql 
          ).getclobval(),','
        ) line_list
      from   rws
      group  by col1,col2;
    
    COL1    COL2     KEY_LIST             LINE_LIST               
          1 txt_1    key_1,key_2,key_3    ,line_1,line_1,line_2    
          2 txt_2    key_4,key_5,key_6    line_3,,line_3,line_4 
    

    【讨论】:

    • @Chris_Saxon 非常感谢,效果很好。我会在真正的查询上试试这个并报告。
    • @chris_saxon 我刚刚发现 tmp_keys.seq 可以为空。所以,如果我运行: update tmp_keys set seq=null;您知道我如何将您的查询更改为按 seq 排序,key1 以使其工作,因为 key_list 现在只包含 1 个值。
    • 所以每个组只有 rn = 1?您需要在数据中找到可用于对值进行分区的其他属性。您要报告line_list 的两个line_1 值的另一个不同值是什么? (如果答案是“什么都没有”,你就卡住了!)
    【解决方案2】:

    在加入表之前对行进行去重和聚合:

    SELECT  m.col1,
            m.col2,
            SUBSTR( k.key_list, 1, LENGTH( k.key_list ) - 1 ) AS key_list,
            SUBSTR( l.line_list, 1, LENGTH( l.line_list ) - 1 ) AS line_list
    FROM    tmp_main m
            JOIN (
              SELECT col1,
                     XMLAGG(
                       XMLELEMENT(E,key1 , ',').EXTRACT('//text()')
                       ORDER BY seq
                     ).GetClobVal() as key_list
              FROM   (
                SELECT k.*,
                       ROW_NUMBER() OVER ( PARTITION BY col1, key1 ORDER BY seq ) AS rn
                FROM   tmp_keys k
              )
              WHERE  rn = 1
              GROUP BY col1
            ) k
            ON m.col1=k.col1
            JOIN (
              SELECT col1,
                     XMLAGG(
                       XMLELEMENT(E,line1 , ',').EXTRACT('//text()')
                       ORDER BY seq
                     ).GetClobVal() as line_list
              FROM   (
                SELECT l.*,
                       ROW_NUMBER() OVER ( PARTITION BY col1, line1 ORDER BY seq ) AS rn
                FROM   tmp_line l
              )
              WHERE  rn = 1
              GROUP BY col1
            ) l
            ON m.col1=l.col1;
    

    另外,当您希望列表中的分隔符之间有空字符串时,不要使用RTRIM,就好像这个空字符串作为列表的终端元素出现一样,那么它将被RTRIM 删除;相反,只需删除最后一个分隔符。

    输出:

    COL1 | COL2 |键列表 | LINE_LIST ---: | :---- | :---------------- | :------------- 1 | txt_1 | key_1,key_2,key_3 | ,line_1,line_2 2 | txt_2 | key_4,key_5,key_6 | line_3,,line_4

    db小提琴here

    【讨论】:

    • 我确实想过这种想法,但它变得非常复杂,我放弃了。非常感谢您的努力。我将尝试您和 chris 的解决方案,看看哪个在实际查询中运行最快(并且最容易扩展为更多列)并报告
    【解决方案3】:

    在实际查询中,表要大得多,我需要添加一个谓词:

    Where m.another_col='xxx'
    

    当我从@MT0 将这个谓词(在最后一行之后)添加到解决方案时,我得到“设备上没有剩余空间”,我怀疑这是因为内联查询首先在整个 tmp_keys 和 tmp_line 表上运行,而且太多在谓词运行之前选择行。 因此,我采用了@chris_saxon 提供的解决方案,在更改后将新谓词添加到 WITH 子查询因子的末尾:

     row_number() over (  partition by m.col1,col2,k.seq order by key1 ) rnk
    

    到:

    row_number() over (  partition by m.col1,col2,key1 order by k.seq,key1 ) rnk
    

    解决 k.seq 可能为空的问题。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-10-25
      • 1970-01-01
      • 1970-01-01
      • 2015-11-20
      • 1970-01-01
      • 2020-11-23
      • 2017-12-10
      相关资源
      最近更新 更多