Oracle - 将一行或多行连接到不同的结果列

Posted

技术标签:

【中文标题】Oracle - 将一行或多行连接到不同的结果列【英文标题】:Oracle - concat one or more rows to different result columns 【发布时间】:2021-10-14 11:08:25 【问题描述】:

我有一个包含如下数据的表结构:

EventNbr | NoteNbr | NoteText
1         1        Example title
1         2        text1
1         3        text2
2         4        Example title 2
3         5        Example title 3
3         6        text3

因此我需要的是一个看起来像这样的数据集

EventNbr | Title         | Notes
1         Example Title    text1,text2
2         Example Title2
3         Example Title3   text3

我基本上从每个EventNbr 中取出最小的NoteNbr 并将其放在Title 列中,然后在MIN 之后的每个其他NoteNbr 将与@987654328 中的逗号连接在一起@专栏。

我目前拥有的作品,但仅适用于具有多个 NoteNbr 行的 EventNbrs。它不适用于只有 one NoteNbr 行的项目,例如上面的 EventNbr 2。

SELECT A.EventNbr,
       MIN(A.NoteText) AS Title,
       LISTAGG(A.NoteText, ',') WITHIN GROUP(ORDER BY A.NoteNbr) AS Notes
  FROM EventNote A
 INNER JOIN (SELECT Min(NoteNbr) Min_NoteNbr, EventNbr
               FROM EventNote
              GROUP BY EventNbr) B
    ON (A.NoteNbr <> B.Min_NoteNbr AND A.EventNbr = B.EventNbr)
 INNER JOIN EventNote C
    ON (C.NoteNbr = B.Min_NoteNbr AND C.EventNbr = B.EventNbr)
 GROUP BY A.EventNbr;

结果:

EventNbr | Title         | Notes
1         Example Title    text1,text2
3         Example Title3   text3

我需要添加什么来考虑只有一个NoteNbr 行的情况?

【问题讨论】:

有些可疑。你说你有一个表,但在你的查询中你有两个表的连接。是哪个? @mathguy 抱歉打错了 - 已修复 【参考方案1】:

可以使用条件聚合和row_number()

select eventnbr,
       max(case when seqnum = 1 then notetext end) as title,
       listagg(case when seqnum > 1 then notetext end, ',') within  group (order by seqnum) as notes
from (select en.*,
             row_number() over (partition by eventnbr order by notenbr) as seqnum
      from eventnote en
     ) en
group by eventnbr;

【讨论】:

【参考方案2】:

最好先运行聚合。这将产生几乎你需要的结果,除了它仍然会在笔记的开头连接标题。这可以在事后纠正,使用标准字符串函数substrinstr 和连接。 (在没有实际注释的情况下,需要后者来处理您提到的“特殊情况”。)

优点是附加操作仅在输出上执行 - 预计(远?)比输入少的行,并且附加操作是微不足道的字符串操作,而不是额外的排序和分区层。

这样的事情 - 假设输入都在一个表中(正如您问题的第一部分所暗示的那样),而不是在不同的表中(如您现有的代码所暗示的那样)。我包含了一个with 子句来模拟输入表。

注意——执行计划表明优化器足够聪明,可以将子查询合并到主查询中;该计划由对聚合 (GROUP BY) 的单个 SELECT 操作组成。 all_notes 在子查询中被替换为 listagg 的长定义,完全消除了外部查询。因此,我们拥有两全其美的优势:一个可以读取的查询,但执行效率尽可能高。

with
  eventnote(eventnbr, notenbr, notetext) as (
    select 1, 1, 'Example title'   from dual union all
    select 1, 2, 'text1'           from dual union all
    select 1, 3, 'text2'           from dual union all
    select 2, 4, 'Example title 2' from dual union all
    select 3, 5, 'Example title 3' from dual union all
    select 3, 6, 'text3'           from dual
  )
select eventnbr, title,
       substr(all_notes, instr(all_notes || ',', ',') + 1) as notes
from   (
         select eventnbr,
                min(notetext) keep (dense_rank first order by notenbr) as title,
                listagg(notetext, ',') within group (order by notenbr) as all_notes
         from   eventnote
         group  by eventnbr
       )
order  by eventnbr
;

EVENTNBR  TITLE            NOTES                         
--------  ---------------  ------------------------------
       1  Example title    text1,text2                   
       2  Example title 2                               
       3  Example title 3  text3                         

【讨论】:

以上是关于Oracle - 将一行或多行连接到不同的结果列的主要内容,如果未能解决你的问题,请参考以下文章

将一个表的两列连接到另一个表的一列

使用 Critera 或 QueryOver API 将一列连接到多个表

数据库多行拼接到一行Oracle和sqlServer

将 Access 表中的多个列连接到一个特定行

如何将带有列表值的熊猫列连接到一个列表中?

使用 codeigniter 将连接表中的列连接到另一列