54

I have a table like this:

group_id  name  
--------  ----
1         David
1         John
1         Alan
1         David
2         Julie
2         Charles

And I want the following result:

group_id  names
--------  -----
1         'Alan, David, John'
2         'Charles, Julie'

I can use the following query:

select group_id, 
       listagg(name, ',') within group (order by name) as names
from demotable
group by group_id 

To get this (very similar result):

group_id  names
--------  -----
1         'Alan, David, David, John'
2         'Charles, Julie'

Any ideas how I can filter the names by uniqueness in the LISTAGG call?

2
  • 3
    Check out the accepted answer in this post: dba.stackexchange.com/questions/696/…
    – roartechs
    Commented Sep 8, 2011 at 22:12
  • 2
    It isn't quite the same.. Answers that work for this question need to be revamped to answer the other question. That isn't to say you can't learn something there to apply to this problem.. Commented Mar 17, 2018 at 0:25

7 Answers 7

58

I don't have an 11g instance available today but could you not use:

SELECT group_id,
       LISTAGG(name, ',') WITHIN GROUP (ORDER BY name) AS names
  FROM (
       SELECT UNIQUE
              group_id,
              name
         FROM demotable
       )
 GROUP BY group_id
3
  • That was my first thought, on this question and looking at the linked DBA question. On this simple data the cost is the same according to autotrace.
    – Alex Poole
    Commented Sep 9, 2011 at 10:30
  • Thanks for your answer! This is correct for the problem. Unfortunately, I was not precise enough in my question formulation and it doesn't help with my real life issue because there are other columns I need from this table that I can't hide with a UNIQUE operator.
    – daveslab
    Commented Sep 9, 2011 at 13:53
  • Nice - painful but it works. You should see the query I have that this is but a small piece of :D oh what a tangled web we weave :D
    – Jim Ford
    Commented Sep 19, 2014 at 13:39
18

Super simple answer - solved!

my full answer here it is now built in in some oracle versions.

select group_id, 
regexp_replace(
    listagg(name, ',') within group (order by name)
    ,'([^,]+)(,\1)*(,|$)', '\1\3')
from demotable
group by group_id;  

This only works if you specify the delimiter to ',' not ', ' ie works only for no spaces after the comma. If you want spaces after the comma - here is a example how.

select 
replace(
    regexp_replace(
     regexp_replace('BBall, BBall, BBall, Football, Ice Hockey ',',\s*',',')            
    ,'([^,]+)(,\1)*(,|$)', '\1\3')
,',',', ') 
from dual

gives BBall, Football, Ice Hockey

10
  • I've used Oracle regexp for a bit but I'm not an expert and I have no clue what's going on here, especially the "\1" in the pattern string. Can someone please explain what's happening here? Thanks in Advance.
    – StewS2
    Commented Apr 23, 2015 at 18:41
  • Actually, this doesn't work for me if the repeated value isn't the first 2 values in the listagg result. For example: this list of sports works (where Baseball is duplicated): Baseball, Basketball - Men, Football, Ice Hockey - Men But this doesn't: Field Hockey, Ultimate Frisbee Club - Women, Ultimate Frisbee Club - Women
    – StewS2
    Commented Apr 23, 2015 at 19:42
  • select regexp_replace('Field Hockey, Ultimate Frisbee Club - Women, Ultimate Frisbee Club - Women','([^,]+)(,\1)+', '\1') from dual returns Field Hockey, Ultimate Frisbee Club - Women seems to work.
    – ozmike
    Commented Apr 27, 2015 at 6:14
  • \1 is the 1st matched term in round brackets ([^,]+) - anything except a comma. so replace \1, \1 with \1 removing duplicates. do worry if you don't understand regexp is a black art.
    – ozmike
    Commented Apr 27, 2015 at 6:18
  • 1
    your right haven't had time to do it yet..!
    – ozmike
    Commented May 5, 2015 at 6:57
5
create table demotable(group_id number, name varchar2(100));
insert into demotable values(1,'David');
insert into demotable values(1,'John');
insert into demotable values(1,'Alan');
insert into demotable values(1,'David');
insert into demotable values(2,'Julie');
insert into demotable values(2,'Charles');
commit;

select group_id, 
       (select listagg(column_value, ',') within group (order by column_value) from table(coll_names)) as names
from (
  select group_id, collect(distinct name) as coll_names 
    from demotable
    group by group_id 
)

GROUP_ID    NAMES
1   Alan,David,John
2   Charles,Julie
2
select group_id, 
       listagg(name, ',') within group (order by name) as names
       over (partition by group_id)   
from demotable
group by group_id 
0
1

below is undocumented and not recomended by oracle. and can not apply in function, show error

select wm_concat(distinct name) as names from demotable group by group_id

regards zia

1
  • WM_concat will not work on oracle 12g by defalut, its a deprecated function. until 11g it will work fine
    – jefissu
    Commented Aug 4, 2017 at 19:30
0

I needed this peace of code as a subquery with some data filter before aggregation based on the outer most query but I wasn't able to do this using the chosen answer code because this filter should go in the inner most select (third level query) and the filter params was in the outer most select (first level query), which gave me the error ORA-00904: "TB_OUTERMOST"."COL": invalid identifier as the ANSI SQL states that table references (correlation names) are scoped to just one level deep.

I needed a solution with no levels of subquery and this one below worked great for me:

with

demotable as
(
  select 1 group_id, 'David'   name from dual union all
  select 1 group_id, 'John'    name from dual union all
  select 1 group_id, 'Alan'    name from dual union all
  select 1 group_id, 'David'   name from dual union all
  select 2 group_id, 'Julie'   name from dual union all
  select 2 group_id, 'Charlie' name from dual
)

select distinct 
  group_id, 
  listagg(name, ',') within group (order by name) over (partition by group_id) names
from demotable
-- where any filter I want
group by group_id, name
order by group_id;
0
-2

In 11g you can use the undocumented function wm_concat like this:

     select wm_concat(distinct name) as names from demotable group by group_id
3
  • 13
    Really, don't use WM_CONCAT! I have been bitten by changes in its behavior between different (minor!) versions of Oracle (11.2.0.1 vs. 11.2.0.2, when Oracle decided that WM_CONCAT should return a CLOB type)
    – Arkady
    Commented Dec 20, 2012 at 19:12
  • yes returns a CLOB - not a string -FYI
    – ozmike
    Commented May 17, 2013 at 1:00
  • 1
    just fyi ... no longer supported in 12c and newer. Commented May 12, 2016 at 14:44

Not the answer you're looking for? Browse other questions tagged or ask your own question.