在Oracle数据库的业务场景中,经常需要将同一分组下的多行字符串数据合并为一个完整的字符串,比如将某个部门下所有员工的姓名合并展示,或者将订单关联的所有商品名称汇总成一个字段,这类需求就是字符串汇总。不同的Oracle版本和场景适合使用不同的汇总方法,下面将逐一介绍。

方法一:使用LISTAGG函数
LISTAGG是Oracle 11g R2版本引入的原生字符串聚合函数,是目前官方推荐的首选字符串汇总方法,语法规范且功能完善。
LISTAGG的基本语法如下:
LISTAGG(需要汇总的字段, 分隔符) WITHIN GROUP (ORDER BY 排序字段) [OVER (PARTITION BY 分组字段)]
如果需要按部门汇总员工姓名,使用逗号作为分隔符,按员工入职时间排序,示例代码如下:
SELECT
dept_id,
LISTAGG(emp_name, ',') WITHIN GROUP (ORDER BY hire_date) AS emp_names
FROM emp_table
GROUP BY dept_id;
LISTAGG的优势是支持自定义分隔符和排序规则,官方维护稳定,但是存在两个限制:一是Oracle 11g R2以下版本不支持,二是汇总的字符串总长度不能超过4000字节,否则会抛出字符串超长错误。
方法二:使用WM_CONCAT函数
WM_CONCAT是Oracle早期版本提供的非公开字符串聚合函数,在Oracle 10g、11g版本中广泛使用,但是Oracle 12c及以后版本已经移除了这个函数,不建议在新项目中使用。
WM_CONCAT的使用非常简单,不需要指定分隔符,默认使用逗号分隔,示例代码如下:
SELECT
dept_id,
WM_CONCAT(emp_name) AS emp_names
FROM emp_table
GROUP BY dept_id;
这个函数的优势是兼容低版本Oracle,不需要复杂配置,但是缺点也很明显:首先是官方不推荐使用,后续版本可能完全移除,其次是默认分隔符无法修改,也不支持自定义排序规则,汇总的字符串同样有长度限制。
方法三:自定义聚合函数
如果需要兼容更低版本的Oracle,或者需要汇总超长字符串避免4000字节的限制,可以自定义聚合函数实现字符串汇总功能。
首先创建一个用于拼接字符串的类型:
CREATE OR REPLACE TYPE str_agg_type AS OBJECT(
total_str CLOB, -- 使用CLOB类型存储超长字符串
STATIC FUNCTION odciaggregateinitialize(sctx IN OUT str_agg_type) RETURN NUMBER,
MEMBER FUNCTION odciaggregateiterate(self IN OUT str_agg_type, value IN VARCHAR2) RETURN NUMBER,
MEMBER FUNCTION odciaggregateterminate(self IN str_agg_type, returnvalue OUT CLOB, flags IN NUMBER) RETURN NUMBER,
MEMBER FUNCTION odciaggregatemerge(self IN OUT str_agg_type, ctx2 IN str_agg_type) RETURN NUMBER
);
/
然后实现这个类型的主体逻辑:
CREATE OR REPLACE TYPE BODY str_agg_type IS
STATIC FUNCTION odciaggregateinitialize(sctx IN OUT str_agg_type) RETURN NUMBER IS
BEGIN
sctx := str_agg_type(NULL);
RETURN odciconst.success;
END odciaggregateinitialize;
MEMBER FUNCTION odciaggregateiterate(self IN OUT str_agg_type, value IN VARCHAR2) RETURN NUMBER IS
BEGIN
IF self.total_str IS NULL THEN
self.total_str := value;
ELSE
self.total_str := self.total_str || ',' || value; -- 自定义分隔符为逗号
END IF;
RETURN odciconst.success;
END odciaggregateiterate;
MEMBER FUNCTION odciaggregateterminate(self IN str_agg_type, returnvalue OUT CLOB, flags IN NUMBER) RETURN NUMBER IS
BEGIN
returnvalue := self.total_str;
RETURN odciconst.success;
END odciaggregateterminate;
MEMBER FUNCTION odciaggregatemerge(self IN OUT str_agg_type, ctx2 IN str_agg_type) RETURN NUMBER IS
BEGIN
IF ctx2.total_str IS NOT NULL THEN
self.total_str := self.total_str || ',' || ctx2.total_str;
END IF;
RETURN odciconst.success;
END odciaggregatemerge;
END;
/
最后创建自定义聚合函数:
CREATE OR REPLACE FUNCTION custom_str_agg(input_str VARCHAR2) RETURN CLOB AGGREGATE USING str_agg_type; /
使用自定义函数的示例:
SELECT
dept_id,
custom_str_agg(emp_name) AS emp_names
FROM emp_table
GROUP BY dept_id;
自定义聚合函数的优势是可以突破4000字节的长度限制,兼容所有Oracle版本,还可以自定义分隔符和拼接逻辑,但是需要手动创建类型和函数,维护成本相对较高。
不同方法的选择建议
可以根据实际场景选择合适的方法:
- 如果使用Oracle 11g R2及以上版本,且汇总字符串长度不超过4000字节,优先选择LISTAGG函数,稳定且易用。
- 如果是老项目且使用Oracle 10g、11g版本,暂时可以保留WM_CONCAT的使用,但新功能不建议引入。
- 如果需要汇总超长字符串,或者需要兼容极低版本的Oracle,选择自定义聚合函数的方案。
在使用字符串汇总功能时,还需要注意如果汇总字段存在NULL值,大部分汇总方法会直接忽略NULL值,不会将其计入结果,如果需要处理NULL值可以提前使用NVL函数将NULL转换为指定内容。