32

I have a table that looks like this:

A 1 
A 2 
B 1 
B 2

And I want to produce a result set that looks like this:

A 1 2 
B 1 2

Is there a SQL statement that will do this? I am using Oracle.

Related questions:

Community
  • 1
  • 1
user128807
  • 10,447
  • 17
  • 53
  • 72
  • http://stackoverflow.com/questions/492563/oracle-combine-multiple-results-in-a-subquery-into-a-single-comma-separated-valu – derobert Feb 03 '11 at 21:38
  • See similar question [advice-using-pivot-table-in-oracle](http://stackoverflow.com/questions/365238/advice-using-pivot-table-in-oracle) – Tony Andrews Jul 13 '09 at 16:56

7 Answers7

28

(WARNING - WM_CONCAT is an unsupported function that was removed in version 12c. Unless you're using a very old database, you should avoid this function. You should probably use LISTAGG instead.)


It depends on the version of Oracle you're using. If it supports the wm_concat() function, then you can simply do something like this:

SELECT field1, wm_concat(field2) FROM YourTable GROUP BY field2;

wm_concat() basically works just like group_concat() in MySQL. It may not be documented, so fire up ye olde sqlplus and see if it's there.

If it isn't there, then you'll want to implement something equivalent yourself. You can find some instructions on how to do this in the string aggregation page at oracle-base.com.

Jon Heller
  • 34,999
  • 6
  • 74
  • 132
John Hyland
  • 6,855
  • 28
  • 32
23

Pretty old topic, but it could help others since Oracle improved in the mean time.

The LISTAGG function is what you are looking for (in 11g at least)

Christian Hudon
  • 1,881
  • 1
  • 21
  • 42
user1973071
  • 231
  • 2
  • 2
  • 2
    select distinct c1, listagg(c2, ' ') within group (order by c2) over (partition by c1) as c2_list from table; – bitmagier May 15 '14 at 15:04
  • url is expired. Those who want to check try this out https://docs.oracle.com/cd/E11882_01/server.112/e41084/functions089.htm#SQLRF30030 – leg0las Aug 17 '20 at 13:48
9

In Oracle 10g+:

SELECT  *
FROM    (
        SELECT  *
        FROM    mytable
        MODEL
        PARTITION BY
                (grouper)
        DIMENSION BY
                (ROW_NUMBER() OVER (PARTITION BY grouper ORDER BY id) AS rn)
        MEASURES
                (val, val AS group_concat, 0 AS mark)
        RULES SEQUENTIAL ORDER (
                group_concat[rn > 1] ORDER BY rn = group_concat[CV() - 1] || ', ' || val[CV()],
                mark[ANY] ORDER BY rn = PRESENTV(mark[CV() + 1], 0, 1)
                )
        )
WHERE   mark = 1
ORDER BY
        grouper

See this article in my blog for explanations:

Quassnoi
  • 413,100
  • 91
  • 616
  • 614
  • You are totally dependent on mytable being sorted by rn. If it is not sorted you'll get an "ORA-32637: Self cyclic rule in sequential order MODEL". So this variant is buggy. You may want to read another model clause variant on my blog: http://rwijk.blogspot.com/2008/05/string-aggregation-with-model-clause.html – Rob van Wijk Jul 29 '09 at 19:20
  • @Rob: `rn` here is just an alias for `ROW_NUMBER()`. I copied it from my article (which used a `CTE` as a rowsource) but didn't put it into the table. Thanks for noticing. – Quassnoi Jul 29 '09 at 20:36
  • Yes, I know. But if, for instance, you used another analytic function with a different order by, and that accompanying WINDOW SORT operation is executed last, you get the aforementioned error. So you still have to use ordered rule evaluation to make this idea work. – Rob van Wijk Jul 29 '09 at 21:15
  • @Rob: right again, updated, checked with `6 - ROW_NUMBER() OVER (PARTITION BY grouper ORDER BY rno) AS rn`. Thanks. – Quassnoi Jul 29 '09 at 21:45
5

Try something like :

SELECT
    field1,
    RTRIM(REPLACE(REPLACE(XMLAgg(XMLElement("x", field2) ORDER BY field2), '<x>'), '</x>', ' ')) AS field2s
  FROM yourTable
  GROUP BY field1

Freely inspired by an answer found in this Oracle forum.

EDIT: this solution proved very resources intensive with requests involving something like 105 rows. I ended up replacing this by custom aggregate functions as suggested by John.

Community
  • 1
  • 1
Mac
  • 8,191
  • 4
  • 40
  • 51
4

If you have got 10g, then you have to go through the function below:

CREATE OR REPLACE FUNCTION get_separated_value (input_val  in  number)
  RETURN VARCHAR2
IS
  return_text  VARCHAR2(10000) := NULL;
BEGIN
  FOR x IN (SELECT col2 FROM table_name WHERE col1 = input_val) LOOP
    return_text := return_text || ' ' || x.col2 ;
  END LOOP;
  RETURN return_text;
END;
/

So, you can do like:

select col1, get_separated_value(col1) from table_name

Fiddle here

If you have got oracle 11g, you can use listagg :

SELECT 
    age,
    LISTAGG(name, ' ') WITHIN GROUP (ORDER BY name) "names"
FROM table_x
GROUP BY age

Fiddle here for Listagg

hsuk
  • 6,770
  • 13
  • 50
  • 80
2

User defined aggregate functions: http://www.adp-gmbh.ch/ora/sql/user_def_agg.html

Just copy/paste and use it. Works on 9i.

jva
  • 2,797
  • 1
  • 26
  • 41
2
SELECT a , COLLECT(b) FROM foo GROUP BY a

very useful when used in pl/sql - can be casted to a user defined collection.

haki
  • 9,389
  • 15
  • 62
  • 110
  • 1
    When I tried it on an 11g database the CAST was required. But I gave you +1 because I didn't know the COLLECT function existed - Oracle adds so many features it's impossible to keep up. – redcayuga Jan 03 '14 at 16:11