Oracle 19c+ 支持 LISTAGG(DISTINCT col, sep) 原生去重语法,高效简洁;低版本不识别该语法会报 ORA-00936,应改用子查询预去重或避免正则替换。

LISTAGG 在 Oracle 19c 中支持直接使用 DISTINCT 关键字去重,这是最简洁、语义最清晰的方式——但必须确认你的数据库版本确实是 19c 或更高(如 21c),且未启用向下兼容模式。
LISTAGG(DISTINCT ...) 是 19c+ 的原生语法
Oracle 19c 开始允许在 LISTAGG 的第一个参数中直接写 DISTINCT column,无需子查询或正则。它会在聚合前自动剔除重复值,再按 WITHIN GROUP (ORDER BY ...) 排序拼接。
示例:
SELECT dept_id,
LISTAGG(DISTINCT emp_name, ', ') WITHIN GROUP (ORDER BY emp_name) AS unique_employees
FROM employees
GROUP BY dept_id;- 该写法在 19c+ 中合法且高效;12c/18c 及更早版本会报
ORA-00936: missing expression -
DISTINCT作用于单列值,不支持多列组合去重(如DISTINCT col1, col2) - 若省略
WITHIN GROUP子句,结果顺序不确定(虽语法允许,但不推荐)
为什么不能在低版本用 LISTAGG(DISTINCT ...)?
因为 DISTINCT 作为 LISTAGG 参数是 19c 新增语法糖,底层由 Oracle 优化器自动改写为去重 + 聚合两步。18c 及以前的解析器根本不识别这个结构。
常见错误现象:
ORA-00936: missing expression -- 出现在执行 LISTAGG(DISTINCT name, ',') WITHIN GROUP(...) 时
- 不是权限或对象问题,是纯语法不支持
-
WM_CONCAT(DISTINCT ...)在 12c+ 已被彻底移除,调用直接报ORA-00904 - 别试图用
CAST(LISTAGG(...) AS CLOB)绕过 —— 语法校验阶段就失败了
替代方案:先去重再 LISTAGG(兼容所有版本)
当无法升级或需兼容旧环境时,用子查询预去重是最稳妥的选择,性能也通常优于正则替换。
SELECT dept_id,
LISTAGG(emp_name, ', ') WITHIN GROUP (ORDER BY emp_name) AS unique_employees
FROM (SELECT DISTINCT dept_id, emp_name FROM employees)
GROUP BY dept_id;- 子查询中的
DISTINCT是标准 SQL,全版本支持 - 注意:
GROUP BY必须包含子查询中所有非聚合列(此处是dept_id) - 如果源表很大,建议在
(dept_id, emp_name)上建组合索引加速去重
正则替换去重只适合小数据量场景
用 REGEXP_REPLACE 清理已拼接字符串里的重复项,本质是“事后补救”,有明显局限:
SELECT dept_id,
REGEXP_REPLACE(
LISTAGG(emp_name, ', ') WITHIN GROUP (ORDER BY emp_name),
'([^,]+)(,\1)+', '\1'
) AS unique_employees
FROM employees
GROUP BY dept_id;- 正则模式
'([^,]+)(,\1)+'只能处理**相邻重复项**,若原始排序后重复值不连续(比如 A,B,A),就漏掉 - 拼接结果超 4000 字节时,
LISTAGG返回CLOB,而REGEXP_REPLACE对CLOB支持不稳定,易报ORA-06502或截断 - 正则引擎开销随字符串长度平方增长,万级拼接项时响应明显变慢
实际用的时候,优先查清 SELECT * FROM v$version。19c+ 就直用 LISTAGG(DISTINCT ...);否则老老实实走子查询去重——别为了少写几行代码,在生产里埋下正则失效或性能雪崩的坑。


















