在数据库操作中,我们经常遇到需要将多行数据整合为一行进行展示或分析的需求,这与Excel中的“合并单元格”在视觉效果上类似,但其背后的逻辑和实现方式完全不同,数据库的核心是结构化存储和高效查询,合并”通常通过SQL查询中的数据聚合或条件连接来实现,而非物理上的单元格合并,本文将详细介绍几种在数据库中将两行或多行数据合并为一行的常用方法。

基础聚合——使用 GROUP BY 子句
这是最常用、最基础的数据合并方式,适用于将具有相同标识符的多行数据进行数学运算(如求和、平均值、计数等)。
场景示例:
假设我们有一个销售记录表 sales_records,记录了不同产品在不同区域的销售额。
| product_id | product_name | region | sales_amount |
|---|---|---|---|
| 1 | 笔记本电脑 | 北京 | 15000 |
| 2 | 智能手机 | 上海 | 8000 |
| 1 | 笔记本电脑 | 上海 | 12000 |
| 2 | 智能手机 | 北京 | 9000 |
需求: 计算每个产品的总销售额,将同一产品的多行记录合并为一行。
SQL 实现:
SELECT
product_name,
SUM(sales_amount) AS total_sales
FROM
sales_records
GROUP BY
product_name;
查询结果: | product_name | total_sales | |--------------|-------------| | 笔记本电脑 | 27000 | | 智能手机 | 17000 |
原理: GROUP BY product_name 子句会将 product_name 相同的行划分为一个组,SUM(sales_amount) 聚合函数对每个组内的 sales_amount 进行求和,最终每个组返回一行结果,除了 SUM(),常用的聚合函数还有 AVG() (平均值), COUNT() (计数), MAX() (最大值), MIN() (最小值)。
字符串聚合——将多行值合并为单个字符串
有时我们希望将多行中的某个字段值连接成一个字符串,将一个产品的所有销售区域合并显示。
场景示例: 基于上面的 sales_records 表。
需求: 将每个产品的所有销售区域合并到一个字段中,用逗号分隔。

SQL 实现(不同数据库语法略有差异):
-
MySQL: 使用
GROUP_CONCAT()SELECT product_name, GROUP_CONCAT(region SEPARATOR ', ') AS all_regions FROM sales_records GROUP BY product_name; -
PostgreSQL / SQL Server: 使用
STRING_AGG()SELECT product_name, STRING_AGG(region, ', ') AS all_regions FROM sales_records GROUP BY product_name; -
Oracle: 使用
LISTAGG()SELECT product_name, LISTAGG(region, ', ') WITHIN GROUP (ORDER BY region) AS all_regions FROM sales_records GROUP BY product_name;
查询结果: | product_name | all_regions | |--------------|------------------| | 笔记本电脑 | 北京, 上海 | | 智能手机 | 上海, 北京 |
原理: 这类函数专门用于将分组内的多行字符串值按照指定的分隔符连接成一个单一的字符串。
条件合并——使用自连接(Self-Join)
当需要合并的行具有明确的、可区分的条件时(一个用户的家庭地址和工作地址),可以使用自连接的方式。
场景示例:
假设有一个用户地址表 user_addresses。
| user_id | address_type | address |
|---|---|---|
| 101 | home | 北京市朝阳区... |
| 101 | work | 北京市海淀区... |
| 102 | home | 上海市浦东新区... |
需求: 将每个用户的家庭地址和工作地址显示在同一行。

SQL 实现:
SELECT
t1.user_id,
t1.address AS home_address,
t2.address AS work_address
FROM
user_addresses t1
LEFT JOIN
user_addresses t2 ON t1.user_id = t2.user_id AND t2.address_type = 'work'
WHERE
t1.address_type = 'home';
查询结果: | user_id | home_address | work_address | |---------|--------------------|--------------------| | 101 | 北京市朝阳区... | 北京市海淀区... | | 102 | 上海市浦东新区... | NULL |
原理: 我们将 user_addresses 表视为两个独立的表(通过别名 t1 和 t2)。t1 用于筛选家庭地址,t2 用于筛选工作地址,然后通过 user_id 将它们连接起来。LEFT JOIN 确保了即使没有工作地址的用户也会被显示出来。
| 方法 | 使用场景 | 关键SQL/函数 | 优点 | 缺点 |
|---|---|---|---|---|
GROUP BY |
对多行数据进行数学统计(求和、计数等) | SUM(), COUNT(), AVG() 等 |
语法标准,几乎所有数据库都支持 | 仅适用于数值聚合,会丢失原始行的详细信息 |
| 字符串聚合 | 将多行文本合并为一个字符串 | GROUP_CONCAT(), STRING_AGG() 等 |
直观实现“多对一”的文本合并 | 函数非标准化,不同数据库语法不同 |
| 自连接 | 合并具有明确配对关系(如类型、状态)的行 | JOIN ... ON ... |
灵活,能保留不同列的详细信息 | SQL较复杂,当配对关系不确定时难以实现 |
相关问答FAQs
为什么数据库不像Excel那样,可以直接提供一个“合并单元格”的功能? 解答: 这是由数据库和电子表格的根本设计理念决定的,Excel是一个二维表格工具,单元格是独立的,可以自由格式化,适合人机交互和轻量级计算,而数据库是关系型数据库管理系统(RDBMS),其核心是存储结构化、关系化的数据,保证数据的完整性、一致性和高效查询,物理上的“合并单元格”会破坏第一范式(1NF),即每个字段都是不可分割的原子值,这会导致数据冗余、更新异常和查询复杂性剧增,数据库通过SQL查询逻辑上“合并”数据,而不是物理上改变存储结构。
如果我想合并的行没有明显的共同标识符,或者我想根据行的顺序进行合并(如合并第1行和第2行),该怎么办?
解答: 这种情况比较特殊,因为关系型数据库的表理论上是无序的行集合,要实现按顺序合并,你需要先为每一行生成一个序号,这时可以使用窗口函数,ROW_NUMBER()。
- 使用
ROW_NUMBER() OVER (ORDER BY some_column)为每一行分配一个唯一的、连续的行号。 - 你可以利用这个行号进行分组或连接,你可以将奇数行与下一行偶数行配对:
GROUP BY FLOOR((row_number - 1) / 2)。 - 或者,创建一个临时表或公用表表达式(CTE),包含行号,然后通过自连接,将
row_number = n的行与row_number = n+1的行连接起来。
这种方法虽然强大,但通常意味着数据模型可能存在设计缺陷,建议优先考虑是否可以在表设计阶段增加一个明确的分组键。