Using SELECT DISTINCT ... as part of a Subquery before calling LISTAGG is probably the best way for simple queries, as noted by @a_horse_with_no_name
However, in more complex queries, it might not be possible, or easy, to accomplish this. I had this come up in a scenario that was using top-n approach using an analytic function.
So I found the COLLECT aggregate function. It is documented to have the UNIQUE or DISTINCT modifier available. Only in 10g, it quietly fails (it ignores the modifier without error). However, to overcome this, from another answer, I came to this solution:
SELECT
...
(
SELECT LISTAGG(v.column_value,',') WITHIN GROUP (ORDER BY v.column_value)
FROM TABLE(columns_tab) v
) AS columns,
...
FROM (
SELECT
...
SET(CAST(COLLECT(UNIQUE some_column ORDER BY some_column) AS tab_typ)) AS columns_tab,
...
)
Basically, by using SET, I remove the duplicates in my collection.
You would still need to define the tab_typ as a basic collection type, and in the case of a VARCHAR, this would be for example:
CREATE OR REPLACE type tab_typ as table of varchar2(100)
/
Also as a correction to the answer from @a_horse_with_no_name on the multi column situation, where you might want to aggregate still on a third (or more) columns:
select
col1,
listagg(CASE rn2 WHEN 1 THEN col2 END, ',') within group (order by col2) AS col2_list,
listagg(CASE rn3 WHEN 1 THEN col3 END, ',') within group (order by col3) AS col3_list,
SUM(col4) AS col4
from (
select
col1,
col2,
row_number() over (partition by col1, col2 order by null) as rn2,
row_number() over (partition by col1, col3 order by null) as rn3
from foo
)
group by col1;
If you would leave the rn = 1 as a where condition to the query, you would aggregate other columns incorrectly.