oracle--多行转为一行的连接手段

今天遇到将多行转为一行的一个操作,多谢oracle开发板的 wildwave 提供了比较通用的解决办法,同时也将自己搜到的这方面资料整理如下,多是用于连接列值的。
String集聚连接技术

需要将多行转换为一行,例子如下:

基础数据:
DEPTNO ENAME
---------- ----------
20 SMITH
30 ALLEN
30 WARD
20 JONES
30 MARTIN
30 BLAKE
10 CLARK
20 SCOTT
10 KING
30 TURNER
20 ADAMS
30 JAMES
20 FORD
10 MILLER

预期输出:

DEPTNO EMPLOYEES
---------- --------------------------------------------------
10 CLARK,KING,MILLER
20 SMITH,FORD,ADAMS,SCOTT,JONES
30 ALLEN,BLAKE,MARTIN,TURNER,JAMES,WARD

* LISTAGG分析函数(11g Release 2)
* WM_CONCAT内建函数
* 自定义函数
* 使用Ref Cursor实现通用函数
* 用户自定义聚集函数
* ROW_NUMBER()和SYS_CONNECT_BY_PATH函数(Oracle 9i)
* COLLECT函数(Oracle 10g)

LISTAGG分析函数(11g Release 2)

Oracle 11g Release 2介绍了LISTAGG 函数,使得聚集连接字符串变得很容易。并且允许使用我们指定连接串中的字段顺序。使用LISTAGG如下:

COLUMN employees FORMAT A50

SELECT deptno, LISTAGG(ename, ',') WITHIN GROUP (ORDER BY ename) AS employees
FROM emp
GROUP BY deptno;

DEPTNO EMPLOYEES
---------- --------------------------------------------------
10 CLARK,KING,MILLER
20 ADAMS,FORD,JONES,SCOTT,SMITH
30 ALLEN,BLAKE,JAMES,MARTIN,TURNER,WARD

3 rows selected.

WM_CONCAT内建函数

如果你的Oracle不是11g Release 2,但是支持WM_CONCAT函数,那么解决上面的问题同样是小菜一碟,使用WM_CONCAT 函数G如下:

COLUMN employees FORMAT A50

SELECT deptno, wm_concat(ename) AS employees
FROM emp
GROUP BY deptno;

DEPTNO EMPLOYEES
---------- --------------------------------------------------
10 CLARK,KING,MILLER
20 SMITH,FORD,ADAMS,SCOTT,JONES
30 ALLEN,BLAKE,MARTIN,TURNER,JAMES,WARD

3 rows selected.

自定义函数

另一个方法是自定义一个函数解决问题。get_employees对于给定部门返回一组员工:

CREATE OR REPLACE FUNCTION get_employees (p_deptno in emp.deptno%TYPE)
RETURN VARCHAR2
IS
l_text VARCHAR2(32767) := NULL;
BEGIN
FOR cur_rec IN (SELECT ename FROM emp WHERE deptno = p_deptno) LOOP
l_text := l_text || ',' || cur_rec.ename;
END LOOP;
RETURN LTRIM(l_text, ',');
END;
/
SHOW ERRORS

COLUMN employees FORMAT A50

SELECT deptno,
get_employees(deptno) AS employees
FROM emp
GROUP by deptno;

DEPTNO EMPLOYEES
---------- --------------------------------------------------
10 CLARK,KING,MILLER
20 SMITH,JONES,SCOTT,ADAMS,FORD
30 ALLEN,WARD,MARTIN,BLAKE,TURNER,JAMES

3 rows selected.

为了改善性能减少函数调用,我们预先过滤行数。.

COLUMN employees FORMAT A50

SELECT e.deptno,
get_employees(e.deptno) AS employees
FROM (SELECT DISTINCT deptno
FROM emp) e;

DEPTNO EMPLOYEES
---------- --------------------------------------------------
10 CLARK,KING,MILLER
20 SMITH,JONES,SCOTT,ADAMS,FORD
30 ALLEN,WARD,MARTIN,BLAKE,TURNER,JAMES

3 rows selected.

使用Ref Cursor实现通用函数

另一个可替代的方法是使用游标变量写一个函数来连接行值。基本和上面一样,只是传入的是一个游标,所以使得它更通用:.

CREATE OR REPLACE FUNCTION concatenate_list (p_cursor IN SYS_REFCURSOR)
RETURN VARCHAR2
IS
l_return VARCHAR2(32767);
l_temp VARCHAR2(32767);
BEGIN
LOOP
FETCH p_cursor
INTO l_temp;
EXIT WHEN p_cursor%NOTFOUND;
l_return := l_return || ',' || l_temp;
END LOOP;
RETURN LTRIM(l_return, ',');
END;
/
SHOW ERRORS

使用如下:

COLUMN employees FORMAT A50

SELECT e1.deptno,
concatenate_list(CURSOR(SELECT e2.ename FROM emp e2 WHERE e2.deptno = e1.deptno)) employees
FROM emp e1
GROUP BY e1.deptno;

DEPTNO EMPLOYEES
---------- --------------------------------------------------
10 CLARK,KING,MILLER
20 SMITH,JONES,SCOTT,ADAMS,FORD
30 ALLEN,WARD,MARTIN,BLAKE,TURNER,JAMES

3 rows selected.

同样的,为了减少函数调用可以预先顾虑一些行。.

COLUMN employees FORMAT A50

SELECT deptno,
concatenate_list(CURSOR(SELECT e2.ename FROM emp e2 WHERE e2.deptno = e1.deptno)) employees
FROM (SELECT DISTINCT deptno
FROM emp) e1;

DEPTNO EMPLOYEES
---------- --------------------------------------------------
10 CLARK,KING,MILLER
20 SMITH,JONES,SCOTT,ADAMS,FORD
30 ALLEN,WARD,MARTIN,BLAKE,TURNER,JAMES

3 rows selected.

用户自定义聚集函数

如果你不想使用内置的函数,你可以自定义聚集函数:

CREATE OR REPLACE TYPE t_string_agg AS OBJECT
(
g_string VARCHAR2(32767),

STATIC FUNCTION ODCIAggregateInitialize(sctx IN OUT t_string_agg)
RETURN NUMBER,

MEMBER FUNCTION ODCIAggregateIterate(self IN OUT t_string_agg,
value IN VARCHAR2 )
RETURN NUMBER,

MEMBER FUNCTION ODCIAggregateTerminate(self IN t_string_agg,
returnValue OUT VARCHAR2,
flags IN NUMBER)
RETURN NUMBER,

MEMBER FUNCTION ODCIAggregateMerge(self IN OUT t_string_agg,
ctx2 IN t_string_agg)
RETURN NUMBER
);
/
SHOW ERRORS


CREATE OR REPLACE TYPE BODY t_string_agg IS
STATIC FUNCTION ODCIAggregateInitialize(sctx IN OUT t_string_agg)
RETURN NUMBER IS
BEGIN
sctx := t_string_agg(NULL);
RETURN ODCIConst.Success;
END;

MEMBER FUNCTION ODCIAggregateIterate(self IN OUT t_string_agg,
value IN VARCHAR2 )
RETURN NUMBER IS
BEGIN
SELF.g_string := self.g_string || ',' || value;
RETURN ODCIConst.Success;
END;

MEMBER FUNCTION ODCIAggregateTerminate(self IN t_string_agg,
returnValue OUT VARCHAR2,
flags IN NUMBER)
RETURN NUMBER IS
BEGIN
returnValue := RTRIM(LTRIM(SELF.g_string, ','), ',');
RETURN ODCIConst.Success;
END;

MEMBER FUNCTION ODCIAggregateMerge(self IN OUT t_string_agg,
ctx2 IN t_string_agg)
RETURN NUMBER IS
BEGIN
SELF.g_string := SELF.g_string || ',' || ctx2.g_string;
RETURN ODCIConst.Success;
END;
END;
/
SHOW ERRORS


CREATE OR REPLACE FUNCTION string_agg (p_input VARCHAR2)
RETURN VARCHAR2
PARALLEL_ENABLE AGGREGATE USING t_string_agg;
/
SHOW ERRORS

使用如下:

COLUMN employees FORMAT A50

SELECT deptno, string_agg(ename) AS employees
FROM emp
GROUP BY deptno;

DEPTNO EMPLOYEES
---------- --------------------------------------------------
10 CLARK,KING,MILLER
20 SMITH,FORD,ADAMS,SCOTT,JONES
30 ALLEN,BLAKE,MARTIN,TURNER,JAMES,WARD

3 rows selected.

ROW_NUMBER()和SYS_CONNECT_BY_PATH函数(Oracle 9i)

使用 ROW_NUMBER() 和SYS_CONNECT_BY_PATH 实现:

SELECT deptno,
LTRIM(MAX(SYS_CONNECT_BY_PATH(ename,','))
KEEP (DENSE_RANK LAST ORDER BY curr),',') AS employees
FROM (SELECT deptno,
ename,
ROW_NUMBER() OVER (PARTITION BY deptno ORDER BY ename) AS curr,
ROW_NUMBER() OVER (PARTITION BY deptno ORDER BY ename) -1 AS prev
FROM emp)
GROUP BY deptno
CONNECT BY prev = PRIOR curr AND deptno = PRIOR deptno
START WITH curr = 1;

DEPTNO EMPLOYEES
---------- --------------------------------------------------
10 CLARK,KING,MILLER
20 ADAMS,FORD,JONES,SCOTT,SMITH
30 ALLEN,BLAKE,JAMES,MARTIN,TURNER,WARD

3 rows selected.

COLLECT函数(Oracle 10g)

使用COLLECT函数,这个需要一个关联数组:

CREATE OR REPLACE TYPE t_varchar2_tab AS TABLE OF VARCHAR2(4000);
/

CREATE OR REPLACE FUNCTION tab_to_string (p_varchar2_tab IN t_varchar2_tab,
p_delimiter IN VARCHAR2 DEFAULT ',') RETURN VARCHAR2 IS
l_string VARCHAR2(32767);
BEGIN
FOR i IN p_varchar2_tab.FIRST .. p_varchar2_tab.LAST LOOP
IF i != p_varchar2_tab.FIRST THEN
l_string := l_string || p_delimiter;
END IF;
l_string := l_string || p_varchar2_tab(i);
END LOOP;
RETURN l_string;
END tab_to_string;
/

使用如下:

COLUMN employees FORMAT A50

SELECT deptno,
tab_to_string(CAST(COLLECT(ename) AS t_varchar2_tab)) AS employees
FROM emp
GROUP BY deptno;

DEPTNO EMPLOYEES
---------- --------------------------------------------------
10 CLARK,KING,MILLER
20 SMITH,JONES,SCOTT,ADAMS,FORD
30 ALLEN,WARD,MARTIN,BLAKE,TURNER,JAMES

3 rows selected.
<think>好的,我现在需要处理用户的问题,关于SQL中将多行数据转换为一行。首先,我需要理解用户的具体需求。用户提到“SQL多行一行”,这通常指的是将多行数据合并成单行,可能涉及到将多个行的某个字段合并成一个字段,或者将多行的数据按某种条件聚合成单行的不同列。 接下来,我要回想常见的SQL技术来解决这个问题。首先想到的是使用聚合函数结合GROUP BY,但普通的聚合函数如SUM、AVG可能不适用,因为用户可能需要将多个值合并成一个字符串或者分散到不同的列中。例如,如果有多个订单项需要合并成一个订单列表,或者将多个行的数据转换为列(如行转列)。 另一个可能性是使用字符串聚合函数,如MySQL中的GROUP_CONCAT,SQL Server中的STRING_AGG,以及Oracle中的LISTAGG。这些函数可以将多行的某个字段值合并成一个字符串,用分隔符连接。比如,将同一个用户的多个爱好合并成一个逗号分隔的字符串。 还有一种情况是行转列,即PIVOT操作,这在SQL Server和Oracle中有支持,但MySQL可能需要使用CASE语句手动实现。例如,将不同类别的销售数据按月份转换为列。 接下来,我需要考虑用户可能的实际应用场景。例如,用户可能有一个订单表,每个订单有多个商品,需要将同一订单的商品合并成一行显示。或者,用户有一个学生成绩表,需要将每个学生的多个科目成绩转换为一行,每个科目一列。 此外,还需要注意不同数据库系统的语法差异。用户可能没有指定使用的数据库,所以需要提供通用的方法或指出不同数据库的函数。例如,GROUP_CONCAT在MySQL中使用,而STRING_AGG在SQL Server 2017及以上版本中使用。 然后,我需要考虑如何处理分组。用户的数据可能需要按某个键(如用户ID、订单ID)分组,然后在每个组内合并多行数据。同时,要注意去重和排序的问题,例如合并时是否需要按特定顺序排列,或者去除重复值。 可能还需要考虑性能问题,尤其是在处理大数据量时,不同的方法可能有不同的效率。例如,使用PIVOT可能比多个CASE语句更高效,但取决于具体数据库的实现。 最后,我需要组织回答的结构,分步骤说明,提供示例代码,并解释关键点,如不同数据库的函数差异、处理分组和排序的方法,以及可能的注意事项,比如字段长度限制或重复值处理。</think>--- 要将 SQL 中多行数据合并为一行,通常涉及**分组聚合**或**行转列**操作。以下是不同场景下的实现方法: --- ### **一、多行文本合并为单行(字符串拼接)** **目标**:将同一分组的多个行的字段值合并为一个字符串。 **示例**:将同一订单的商品名称合并为逗号分隔的字符串。 #### **实现方法** 1. **MySQL**:使用 `GROUP_CONCAT` ```sql SELECT order_id, GROUP_CONCAT(product_name SEPARATOR ', ') AS products FROM orders GROUP BY order_id; ``` 2. **SQL Server**:使用 `STRING_AGG`(SQL Server 2017+) ```sql SELECT order_id, STRING_AGG(product_name, ', ') AS products FROM orders GROUP BY order_id; ``` 3. **Oracle**:使用 `LISTAGG` ```sql SELECT order_id, LISTAGG(product_name, ', ') WITHIN GROUP (ORDER BY product_name) AS products FROM orders GROUP BY order_id; ``` --- ### **二、多行转列(行转列,PIVOT)** **目标**:将多行数据按某个字段的值转换为多列。 **示例**:将学生各科成绩从多行(每行一科)转为单行(每列一科)。 #### **实现方法** 1. **标准SQL(适用于所有数据库)**:使用 `CASE WHEN` ```sql SELECT student_id, MAX(CASE WHEN subject = '数学' THEN score ELSE NULL END) AS math_score, MAX(CASE WHEN subject = '语文' THEN score ELSE NULL END) AS chinese_score, MAX(CASE WHEN subject = '英语' THEN score ELSE NULL END) AS english_score FROM scores GROUP BY student_id; ``` 2. **SQL Server/Oracle**:使用 `PIVOT` 关键字 ```sql SELECT * FROM scores PIVOT ( MAX(score) FOR subject IN ('数学' AS math, '语文' AS chinese, '英语' AS english) ); ``` --- ### **三、多行合并为JSON/数组** **目标**:将多行数据合并为一个结构化字段(如JSON数组)。 **示例**:将用户的所有地址合并为JSON数组。 #### **实现方法(PostgreSQL示例)** ```sql SELECT user_id, JSON_AGG(address) AS addresses FROM user_addresses GROUP BY user_id; ``` --- ### **关键注意事项** 1. **去重与排序** - 在拼接字符串时,可用 `DISTINCT` 去重(如 `GROUP_CONCAT(DISTINCT product_name)`)。 - 指定排序规则(如 `GROUP_CONCAT(product_name ORDER BY date ASC)`)。 2. **字段长度限制** - 字符串拼接结果可能受数据库配置限制(如MySQL的 `group_concat_max_len`)。 3. **动态列名(PIVOT场景)** - 若列名不固定(如科目动态变化),需通过动态SQL生成查询语句。 --- ### **总结** | 场景 | 方法 | 适用数据库 | |----------------------|-------------------------|--------------------------| | 多行合并为字符串 | `GROUP_CONCAT/STRING_AGG` | MySQL/SQL Server/Oracle | | 多行转为多列 | `PIVOT` 或 `CASE WHEN` | 通用/SQL Server/Oracle | | 多行合并为结构化数据 | `JSON_AGG/ARRAY_AGG` | PostgreSQL/MySQL 8.0+ | 根据实际需求选择合适方法,并注意不同数据库的语法差异。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值