【问题标题】:SQL Query to concatenate column values from multiple rows in Oracle用于连接 Oracle 中多行的列值的 SQL 查询
【发布时间】:2011-06-08 20:42:57
【问题描述】:

是否可以构造 SQL 来连接来自 多行?

下面是一个例子:

表 A

PID 一种 乙 C

表 B

PID 序列描述 A 1 有 一个 2 一个不错 3天。 B 1 干得好。 C 1 是 C 2 我们可以 C 3 做 C 4 这个工作!

SQL 的输出应该是 -

PID 描述 A祝你有美好的一天。 B 干得好。 C 是的,我们可以做这项工作!

所以基本上输出表的 Desc 列是表 B 中 SEQ 值的串联?

对 SQL 有帮助吗?

【问题讨论】:

标签: sql oracle string-aggregation


【解决方案1】:

根据您拥有的版本,有几种方法 - 请参阅oracle documentation on string aggregation techniques。一个很常见的就是使用LISTAGG

SELECT pid, LISTAGG(Desc, ' ') WITHIN GROUP (ORDER BY seq) AS description
FROM B GROUP BY pid;

然后加入A,挑选你想要的pids

注意:开箱即用,LISTAGG 仅适用于 VARCHAR2 列。

【讨论】:

  • Oracle 10g 使用 wm_concat() 以逗号分隔的序列号的升序连接文本,我们可以用其他东西分隔降序吗?
【解决方案2】:

带 SQL 模型子句:

SQL> select pid
  2       , ltrim(sentence) sentence
  3    from ( select pid
  4                , seq
  5                , sentence
  6             from b
  7            model
  8                  partition by (pid)
  9                  dimension by (seq)
 10                  measures (descr,cast(null as varchar2(100)) as sentence)
 11                  ( sentence[any] order by seq desc
 12                    = descr[cv()] || ' ' || sentence[cv()+1]
 13                  )
 14         )
 15   where seq = 1
 16  /

P SENTENCE
- ---------------------------------------------------------------------------
A Have a nice day
B Nice Work.
C Yes we can do this work!

3 rows selected.

我写过这个here。如果您点击 OTN 线程的链接,您会发现更多内容,包括性能比较。

【讨论】:

    【解决方案3】:

    LISTAGG 分析函数是在 Oracle 11g 第 2 版 中引入的,使得聚合字符串变得非常容易。 如果您使用的是 11g 第 2 版,则应使用此函数进行字符串聚合。 有关字符串连接的更多信息,请参阅下面的 url。

    http://www.oracle-base.com/articles/misc/StringAggregationTechniques.php

    String Concatenation

    【讨论】:

      【解决方案4】:

      还有一个 XMLAGG 函数,它适用于 11.2 之前的版本。因为WM_CONCATundocumented and unsupported by Oracle,所以建议不要在生产系统中使用。

      使用XMLAGG,您可以执行以下操作:

      SELECT XMLAGG(XMLELEMENT(E,ename||',')).EXTRACT('//text()') "Result" 
      FROM employee_names
      

      这是做什么的

      • employee_names 表中ename 列的值(用逗号连接)放在一个xml 元素中(带有标签E)
      • 提取此文本
      • 聚合 xml(连接它)
      • 调用结果列“结果”

      【讨论】:

      • XMLAGG 适用于 Oracle 12.2。此外,XLMAGG 允许连接很长的字符串,而 LISTAGG 可能因为它们的最终长度而无法连接。
      【解决方案5】:

      在运行选择查询之前,运行以下命令:

      SET SERVEROUT ON SIZE 6000

      SELECT XMLAGG(XMLELEMENT(E,SUPLR_SUPLR_ID||',')).EXTRACT('//text()') "SUPPLIER" 
      FROM SUPPLIERS;
      

      【讨论】:

        【解决方案6】:

        试试这个代码:

         SELECT XMLAGG(XMLELEMENT(E,fieldname||',')).EXTRACT('//text()') "FieldNames"
            FROM FIELD_MASTER
            WHERE FIELD_ID > 10 AND FIELD_AREA != 'NEBRASKA';
        

        【讨论】:

          【解决方案7】:
          1. 如果必须进行排序,则 LISTAGG 提供最佳性能(00:00:05.85)

            SELECT pid, LISTAGG(Desc, ' ') WITHIN GROUP (ORDER BY seq) AS description FROM B GROUP BY pid;

          2. 如果不需要排序,COLLECT 会提供最佳性能(00:00:02.90):

            SELECT pid, TO_STRING(CAST(COLLECT(Desc) AS varchar2_ntt)) AS Vals FROM B GROUP BY pid;

          3. COLLECT with ordering 有点慢(00:00:07.08):

            SELECT pid, TO_STRING(CAST(COLLECT(Desc ORDER BY Desc) AS varchar2_ntt)) AS Vals FROM B GROUP BY pid;

          所有其他技术都较慢。

          【讨论】:

          • 详细说明你的答案会很有帮助。
          • 约翰,我不想重复这篇文章,但简而言之,结果如下:1. 如果必须进行排序,LISTAGG 提供了最佳性能(00:00:05.85) 2. COLLECT如果不需要排序(00:00:02.90),则提供最佳性能:SELECT pid, TO_STRING(CAST(COLLECT(Desc) AS varchar2_ntt)) AS Vals FROM B GROUP BY pid; 3. COLLECT with ordering is bit slow(00:00:07.08): SELECT pid, TO_STRING(CAST(COLLECT(Desc ORDER BY Desc) AS varchar2_ntt)) AS Vals FROM B GROUP BY pid;所有其他技术都较慢。
          • 您可以编辑您的答案以包含相关信息。
          • 我编辑太晚了,这就是我再次添加它的原因。对不起,我是新来的,刚刚开始掌握它。
          【解决方案8】:

          在选择要连接的位置,调用 SQL 函数。

          例如:

          select PID, dbo.MyConcat(PID)
             from TableA;
          

          那么对于SQL函数:

          Function MyConcat(@PID varchar(10))
          returns varchar(1000)
          as
          begin
          
          declare @x varchar(1000);
          
          select @x = isnull(@x +',', @x, @x +',') + Desc
            from TableB
              where PID = @PID;
          
          return @x;
          
          end
          

          Function Header 语法可能是错误的,但原理确实有效。

          【讨论】:

          • 这对Oracle无效
          【解决方案9】:

          对于必须使用 Oracle 9i(或更早版本)解决此问题的用户,您可能需要使用 SYS_CONNECT_BY_PATH,因为 LISTAGG 不可用。

          为了回答 OP,以下查询将显示表 A 中的 PID 并连接表 B 中的所有 DESC 列:

          SELECT pid, SUBSTR (MAX (SYS_CONNECT_BY_PATH (description, ', ')), 3) all_descriptions
          FROM (
                 SELECT ROW_NUMBER () OVER (PARTITION BY pid ORDER BY pid, seq) rnum, pid, description
                 FROM (
                        SELECT a.pid, seq, description
                        FROM table_a a, table_b b
                        WHERE a.pid = b.pid(+)
                       )
                )
          START WITH rnum = 1
          CONNECT BY PRIOR rnum = rnum - 1 AND PRIOR pid = pid
          GROUP BY pid
          ORDER BY pid;
          

          也可能存在键和值都包含在一个表中的情况。在没有表 A 且只存在表 B 的情况下,可以使用以下查询:

          SELECT pid, SUBSTR (MAX (SYS_CONNECT_BY_PATH (description, ', ')), 3) all_descriptions
          FROM (
                 SELECT ROW_NUMBER () OVER (PARTITION BY pid ORDER BY pid, seq) rnum, pid, description
                 FROM (
                        SELECT pid, seq, description
                        FROM table_b
                       )
                )
          START WITH rnum = 1
          CONNECT BY PRIOR rnum = rnum - 1 AND PRIOR pid = pid
          GROUP BY pid
          ORDER BY pid;
          

          所有值都可以根据需要重新排序。单独的连接描述可以在 PARTITION BY 子句中重新排序,PID 列表可以在最终的 ORDER BY 子句中重新排序。


          或者:有时您可能希望将整个表中的所有值连接到一行中。

          这里的关键思想是为要连接的描述组使用人工值。

          在以下查询中,使用了常量字符串 '1',但任何值都可以:

          SELECT SUBSTR (MAX (SYS_CONNECT_BY_PATH (description, ', ')), 3) all_descriptions
          FROM (
                 SELECT ROW_NUMBER () OVER (PARTITION BY unique_id ORDER BY pid, seq) rnum, description
                 FROM (
                        SELECT '1' unique_id, b.pid, b.seq, b.description
                        FROM table_b b
                       )
                )
          START WITH rnum = 1
          CONNECT BY PRIOR rnum = rnum - 1;
          

          可以在 PARTITION BY 子句中重新排序各个连接描述。

          此页面上的其他几个答案也提到了这个非常有用的参考: https://oracle-base.com/articles/misc/string-aggregation-techniques

          【讨论】:

            【解决方案10】:

            正如大多数答案所暗示的那样,LISTAGG 是显而易见的选择。但是,LISTAGG 的一个恼人之处在于,如果连接字符串的总长度超过 4000 个字符(SQL 中 VARCHAR2 的限制),则会引发以下错误,这在 Oracle 版本高达 12.1 中很难管理

            ORA-01489: 字符串连接的结果太长

            12cR2 中添加的一个新特性是LISTAGGON OVERFLOW 子句。 包含此子句的查询如下所示:

            SELECT pid, LISTAGG(Desc, ' ' on overflow truncate) WITHIN GROUP (ORDER BY seq) AS desc
            FROM B GROUP BY pid;
            

            以上将限制输出为 4000 个字符,但不会抛出 ORA-01489 错误。

            这些是ON OVERFLOW 子句的一些附加选项:

            • ON OVERFLOW TRUNCATE 'Contd..' :这将显示 'Contd..' 在 字符串结尾(默认为...
            • ON OVERFLOW TRUNCATE '' :这将显示 4000 个字符 没有任何终止字符串。
            • ON OVERFLOW TRUNCATE WITH COUNT :这将显示总数 终止字符之后的末尾字符数。 例如:-'...(5512)'
            • ON OVERFLOW ERROR :如果您希望 LISTAGG 失败 ORA-01489 错误(无论如何都是默认的)。

            【讨论】:

              猜你喜欢
              • 2014-01-02
              • 1970-01-01
              • 2023-03-14
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2016-07-18
              • 1970-01-01
              • 2021-09-23
              相关资源
              最近更新 更多