Home > Software engineering >  Concat columns from multiple tables into one row without duplicates
Concat columns from multiple tables into one row without duplicates

Time:12-02

I need to concatenate two columns from diffrent tables, delimited with ";" into one row without duplicates.

Table 1:

Name
John;Sue

Table 2:

Name
Mary;John

Desired output

Names
John;Sue;Mary

I tried with :

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

but I get "ORA-01489: result of string concatenation is too long" error.

How to do that properly in Oracle?

CodePudding user response:

You can do it with simple string functions:

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;

Which, for the sample data:

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

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

Outputs:

ID NAME
1 John;Mary;Sue

Note: you can do it with regular expressions; however, for a large dataset, it is likely to be of an order of magnitude slower.


Update

How to do that properly in Oracle?

Do not store delimited strings and store the data in first normal form (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;

Then the query is simply:

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

db<>fiddle here

CodePudding user response:

Presuming those are names and the result doesn't span over more than 4000 characters (which is the listagg limit) then one option is to do this (read comments within code):

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>

If it really spans over more than 4000 characters, switch to XMLAGG; lines #25 onward would be

 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>

CodePudding user response:

You can use a XML-style tecnique before applying LISTAGG() in order to provide writing distinct names such as

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

  • Related