【问题标题】:Concat columns from multiple tables into one row without duplicates将多个表中的列连接成一行,不重复
【发布时间】:2022-01-08 22:55:31
【问题描述】:

我需要连接来自不同表的两列,用“;”分隔成一行,没有重复。

表 1:

Name
John;Sue

表 2:

Name
Mary;John

期望的输出

Names
John;Sue;Mary

我试过了:

select listagg(a.Name, ';') within group (order by a.Name) as Names
from Table1 a
join Table2 b on a.id = b.id;

但我收到“ORA-01489:字符串连接的结果太长”错误。

如何在 Oracle 中正确地做到这一点?

【问题讨论】:

  • “如何在 Oracle 中正确地做到这一点?”正确的方法是不要将数据存储在分隔的字符串中。

标签: sql oracle oracle11g listagg


【解决方案1】:

假设这些 名称并且结果不超过 4000 个字符(这是 listagg 限制),那么一种选择是这样做(在代码中读取 cmets):

SQL> with
  2  -- sample data
  3  table1 (id, name) as
  4    (select 1, 'John;Sue'    from dual union all
  5     select 2, 'Little;Foot' from dual),
  6  table2 (id, name) as
  7    (select 1, 'Mary;John' from dual),
  8  --
  9  union_jack (id, name) as
 10    -- union those two tables
 11    (select id, name from table1
 12     union
 13     select id, name from table2
 14    ),
 15  distname as
 16    -- distinct names
 17    (select distinct
 18            id,
 19            regexp_substr(name, '[^;]+', 1, column_value) name
 20     from union_jack cross join
 21          table(cast(multiset(select level from dual
 22                              connect by level <= regexp_count(name, ';') + 1
 23                             ) as sys.odcinumberlist))
 24    )
 25  select id,
 26         listagg(d.name, ';') within group (order by d.name) as names
 27  from distname d
 28  group by id;

        ID NAMES
---------- ------------------------------
         1 John;Mary;Sue
         2 Foot;Little

SQL>

如果真的超过4000个字符,切换到XMLAGG;第 25 行以后将是

 25  select id,
 26         rtrim(xmlagg (xmlelement (e, d.name || ';') order by d.name).extract
 27                  ('//text()'), ';') as names
 28  from distname d
 29  group by id;

        ID NAMES
---------- ------------------------------
         1 John;Mary;Sue
         2 Foot;Little

SQL>

【讨论】:

  • 感谢您的回答。我认为您的第一个答案是我需要的,但是一个问题。当我的两个表都设置了 max 时,为什么我需要所有这些代码。 70 字节 用于有问题的列? listagg 函数如何达到最大 4000 字节的限制。两者的限制可以是 140 字节?显然我对 listagg 函数的作用感到困惑。
  • “所有这些代码”,因为您必须将名称(由分号分隔)拆分为行,以便您可以仅提取每个 ID 的不同值,然后 然后 将它们聚合回去。截至超出限制:我不知道,您发布的代码仅返回一个表的列值(来自 table1)。
【解决方案2】:

你可以用简单的字符串函数来做到这一点:

WITH t1_positions (id, name, spos, epos) AS (
  SELECT id,
         name,
         1,
         INSTR(name, ';', 1)
  FROM   table1
UNION ALL
  SELECT id,
         name,
         epos + 1,
         INSTR(name, ';', epos + 1)
  FROM   t1_positions
  WHERE  epos > 0
),
t1_strings (id, item) AS (
  SELECT id,
         CASE epos
         WHEN 0
         THEN SUBSTR(name, spos)
         ELSE SUBSTR(name, spos, epos - spos)
         END
  FROM   t1_positions
),
t2_positions (id, name, spos, epos) AS (
  SELECT id,
         name,
         1,
         INSTR(name, ';', 1)
  FROM   table2
UNION ALL
  SELECT id,
         name,
         epos + 1,
         INSTR(name, ';', epos + 1)
  FROM   t2_positions
  WHERE  epos > 0
),
t2_strings (id, item) AS (
  SELECT id,
         CASE epos
         WHEN 0
         THEN SUBSTR(name, spos)
         ELSE SUBSTR(name, spos, epos - spos)
         END
  FROM   t2_positions
)
SELECT id,
       LISTAGG(item, ';') WITHIN GROUP (ORDER BY item) AS name
FROM   (SELECT * FROM t1_strings
        UNION
        SELECT * FROM t2_strings)
GROUP BY id;

其中,对于样本数据:

CREATE TABLE Table1 (id, name) AS
SELECT 1, 'John;Sue' FROM DUAL;

CREATE TABLE Table2 (id, name) AS
SELECT 1, 'Mary;John' FROM DUAL;

输出:

ID NAME
1 John;Mary;Sue

注意:你可以用正则表达式来做;但是,对于大型数据集,它可能会慢一个数量级。


更新

如何在 Oracle 中正确地做到这一点?

不存储分隔字符串,以第一范式(1NF)存储数据:

CREATE TABLE table1 (id, name) AS
SELECT 1, 'John' FROM DUAL UNION ALL
SELECT 1, 'Sue' FROM DUAL;

CREATE TABLE table2 (id, name) AS
SELECT 1, 'Mary' FROM DUAL UNION ALL
SELECT 1, 'John' FROM DUAL;

那么查询很简单:

SELECT id,
       LISTAGG(name, ';') WITHIN GROUP (ORDER BY name) AS name
FROM   (SELECT * FROM table1
        UNION
        SELECT * FROM table2)
GROUP BY id;

db小提琴here

【讨论】:

  • @MTO,感谢您的回答。这个更容易一些,但仍然不是单行的 :).... 我没想到这几天在 SQL 中会出现这样的问题。再次感谢!
  • @Lucy82 添加了有关如何使其更简单的更新;它涉及将数据存储在 1NF 中。 (如果你想要一个单行,那么只需将所有回车替换为查询中的空格......但这将是一个很长的行。)
【解决方案3】:

您可以在应用 LISTAGG() 之前使用 XML 样式的技术,以便提供不同的名称,例如

WITH t AS
( 
  SELECT RTRIM(DBMS_XMLGEN.CONVERT(
                 XMLAGG( 
                        XMLELEMENT(e,name||';')
                       ).EXTRACT('//text()').GETCLOBVAL() ,1),
                ';') AS name  
      FROM ( SELECT t1.name||';'||t2.name AS name
               FROM table1 t1 JOIN table2 t2 ON t1.id=t2.id )
)
 SELECT LISTAGG(REGEXP_SUBSTR(name,'[^;]+',1,level),';') 
        WITHIN GROUP (ORDER BY 0) AS "Names"
   FROM t
CONNECT BY level <= REGEXP_COUNT(name,';')                               

Demo

【讨论】:

  • 感谢大家。我选择了第一个答案作为正确的答案,尽管它们可能都是正确的。再次感谢!
猜你喜欢
  • 1970-01-01
  • 2013-03-28
  • 1970-01-01
  • 2019-09-03
  • 2017-07-23
  • 1970-01-01
  • 1970-01-01
  • 2021-05-20
  • 1970-01-01
相关资源
最近更新 更多