sql 查询按照分组将多条记录转换成单条记录的group_contact()函数

本文介绍了SQL中的CONCAT和GROUP_CONCAT函数用法,包括如何连接字符串、指定分隔符及处理NULL值等,并通过示例展示了如何利用这些函数进行数据整理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、CONCAT()函数
CONCAT()函数用于将多个字符串连接成一个字符串。
使用数据表Info作为示例,其中SELECT id,name FROM info LIMIT 1;的返回结果
+----+--------+
| id | name   |
+----+--------+
|  1 | BioCyc |
+----+--------+
1、语法及使用特点:
CONCAT(str1,str2,…)                       
返回结果为连接参数产生的字符串。如有任何一个参数为NULL ,则返回值为 NULL。可以有一个或多个参数。

2、使用示例:
SELECT CONCAT(id, ‘,’, name) AS con FROM info LIMIT 1;返回结果为
+----------+
| con      |
+----------+
| 1,BioCyc |
+----------+

SELECT CONCAT(‘My’, NULL, ‘QL’);返回结果为
+--------------------------+
| CONCAT('My', NULL, 'QL') |
+--------------------------+
| NULL                     |
+--------------------------+

3、如何指定参数之间的分隔符
使用函数CONCAT_WS()。使用语法为:CONCAT_WS(separator,str1,str2,…)
CONCAT_WS() 代表 CONCAT With Separator ,是CONCAT()的特殊形式。第一个参数是其它参数的分隔符。分隔符的位置放在要连接的两个字符串之间。分隔符可以是一个字符串,也可以是其它参数。如果分隔符为 NULL,则结果为 NULL。函数会忽略任何分隔符参数后的 NULL 值。但是CONCAT_WS()不会忽略任何空字符串。 (然而会忽略所有的 NULL)。

如SELECT CONCAT_WS('_',id,name) AS con_ws FROM info LIMIT 1;返回结果为
+----------+
| con_ws   |
+----------+
| 1_BioCyc |
+----------+

SELECT CONCAT_WS(',','First name',NULL,'Last Name');返回结果为
+----------------------------------------------+
| CONCAT_WS(',','First name',NULL,'Last Name') |
+----------------------------------------------+
| First name,Last Name                         |
+----------------------------------------------+

二、GROUP_CONCAT()函数
GROUP_CONCAT函数返回一个字符串结果,该结果由分组中的值连接组合而成。
使用表info作为示例,其中语句SELECT locus,id,journal FROM info WHERE locus IN('AB086827','AF040764');的返回结果为
+----------+----+--------------------------+
| locus    | id | journal                  |
+----------+----+--------------------------+
| AB086827 |  1 | Unpublished              |
| AB086827 |  2 | Submitted (20-JUN-2002)  |
| AF040764 | 23 | Unpublished              |
| AF040764 | 24 | Submitted (31-DEC-1997)  |
+----------+----+--------------------------+

1、使用语法及特点:
GROUP_CONCAT([DISTINCT] expr [,expr ...]
[ORDER BY {unsigned_integer | col_name | formula} [ASC | DESC] [,col ...]]
[SEPARATOR str_val])
在 MySQL 中,你可以得到表达式结合体的连结值。通过使用 DISTINCT 可以排除重复值。如果希望对结果中的值进行排序,可以使用 ORDER BY 子句。
SEPARATOR 是一个字符串值,它被用于插入到结果值中。缺省为一个逗号 (","),可以通过指定 SEPARATOR "" 完全地移除这个分隔符。
可以通过变量 group_concat_max_len 设置一个最大的长度。在运行时执行的句法如下: SET [SESSION | GLOBAL] group_concat_max_len = unsigned_integer;
如果最大长度被设置,结果值被剪切到这个最大长度。如果分组的字符过长,可以对系统参数进行设置:SET @@global.group_concat_max_len=40000;

2、使用示例:
语句 SELECT locus,GROUP_CONCAT(id) FROM info WHERE locus IN('AB086827','AF040764') GROUP BY locus; 的返回结果为
+----------+------------------+
| locus    | GROUP_CONCAT(id) |
+----------+------------------+
| AB086827 | 1,2              |
| AF040764 | 23,24            |
+----------+------------------+

语句 SELECT locus,GROUP_CONCAT(distinct id ORDER BY id DESC SEPARATOR '_') FROM info WHERE locus IN('AB086827','AF040764') GROUP BY locus;的返回结果为
+----------+----------------------------------------------------------+
| locus    | GROUP_CONCAT(distinct id ORDER BY id DESC SEPARATOR '_') |
+----------+----------------------------------------------------------+
| AB086827 | 2_1                                                      |
| AF040764 | 24_23                                                    |
+----------+----------------------------------------------------------+

语句SELECT locus,GROUP_CONCAT(concat_ws(', ',id,journal) ORDER BY id DESC SEPARATOR '. ') FROM info WHERE locus IN('AB086827','AF040764') GROUP BY locus;的返回结果为
+----------+--------------------------------------------------------------------------+
| locus    | GROUP_CONCAT(concat_ws(', ',id,journal) ORDER BY id DESC SEPARATOR '. ') |
+----------+--------------------------------------------------------------------------+
| AB086827 | 2, Submitted (20-JUN-2002). 1, Unpublished                               |
| AF040764 | 24, Submitted (31-DEC-1997) . 23, Unpublished                            |
+----------+--------------------------------------------------------------------------+
<think>我们有一个需求:根据个人ID将多条简历信息合并为一条记录,使用分号分隔并换行显示。 根据之前的数据库设计,我们有一个person表(存储个人基本信息)和一个resume表(存储每个人的多条简历记录)。 现在,我们想要为每个人生成一条记录,该记录包含这个人的基本信息以及他所有的简历内容(每个简历内容用分号分隔,并且每个简历内容换行显示)。 思路: 1. 我们需要将同一个人的多条简历记录合并成一个字段,字段内容为所有简历内容的组合。 2. 组合的方式:每条简历内容之间用分号(;)分隔,并且每条简历内容单独一行(即每个简历内容后面加上换行符,但注意最后一条简历后面不需要换行)。 但是,由于我们最终是要在一个字段里显示,所以实际上我们是将多条记录合并成一个字符串,这个字符串中包含了换行符。 在SQL中,我们可以使用GROUP_CONCAT函数(在MySQL中)来实现字符串的聚合。同时,我们可以使用CONCAT函数将每个简历的标题和内容组合起来,并加上换行符。 但是注意:GROUP_CONCAT默认使用逗号分隔,我们可以指定分隔符为分号和换行符(即使用'; \n')。但是,这样会在每条记录后面都加上分号和换行符,包括最后一条。为了避免最后一条后面也有分号,我们可以先为每条简历记录生成一个带换行的字符串,然后用GROUP_CONCAT以空字符串连接(这样就不会有额外的分隔符),然后我们再在应用层处理或者使用字符串函数去掉最后一个分号?但是这样并不好,因为GROUP_CONCAT只能指定一个分隔符。 另一种做法:我们可以在每条简历记录生成时,将内容格式化为:简历标题 + 内容 + 换行符,然后用GROUP_CONCAT以分号连接。这样每条记录后面会有一个分号,包括最后一条。然后我们在应用层去掉最后一个分号,或者用字符串函数去掉。但是这样会使得最后一条记录后面多一个分号。 为了避免最后一条多一个分号,我们可以使用以下技巧: - 在GROUP_CONCAT中,我们使用分号加换行作为分隔符,然后整个字符串最后我们去掉最后一个分号和换行(但这样在SQL中处理比较麻烦)。 更简洁的做法:在应用层处理。我们先在数据库里用GROUP_CONCAT以分号和换行连接,然后在应用层去掉末尾的分号和换行。但是,如果我们在数据库查询中直接返回这个字符串,那么我们可以使用TRIM函数去掉末尾的特定字符,但是TRIM只能去掉空格或者指定的单个字符,不能去掉字符串。 因此,我们可以这样: 使用:GROUP_CONCAT( CONCAT(r.resume_title, ':', r.content) SEPARATOR ';\n') 这样会得到:标题1:内容1;\n标题2:内容2;\n标题3:内容3 注意最后一条后面也有分号和换行,但我们并不希望最后一条有分号。所以我们可以用字符串函数去掉最后一个分号。 但是,在MySQL中,我们可以使用SUBSTRING函数和LENGTH函数来去掉最后两个字符(分号和换行符占两个字符)?但是这样并不安全,因为如果只有一条记录,那么就不需要去掉分号,因为这条记录后面不应该有分号。 所以,我们可以在每条记录生成时不加分隔符,然后使用GROUP_CONCAT连接,连接时用分号和换行符,这样最后一条记录后面也会有一个分号和换行符。然后我们在应用层处理:将整个字符串的最后一个分号和换行符去掉。但是这样在SQL查询结果会多出两个字符。 或者,我们可以使用以下方法(在MySQL 8.0+中可以使用窗口函数,但这里我们不使用窗口函数): 使用GROUP_CONCAT,然后使用TRIM(TRAILING ';\n' FROM ...) 来去掉末尾的'; \n'。但是TRIM函数不支持去掉字符串,只支持去掉单个字符。 所以,我们换一种思路:在每条记录生成时,我们给它加上分号和换行,然后通过GROUP_CONCAT连接,最后我们使用SUBSTRING函数去掉最后两个字符(即最后一个分号和换行符)。但是这样如果只有一条记录,那么这条记录后面也会被去掉两个字符,而这条记录后面原本没有分号和换行,所以这样不行。 因此,我们考虑在应用层处理,因为这样更灵活。我们在数据库查询中,将聚合后的字符串原样返回,然后在应用层,如果字符串不为空,我们就去掉最后两个字符(因为最后一条记录后面有分号和换行符)。但是,如果只有一条记录,那么这条记录后面也会被加上分号和换行符,然后我们又去掉,这样就会去掉我们并不想去掉的内容(因为一条记录时,我们并不需要分号分隔,所以也不应该加分隔符)。所以我们需要在GROUP_CONCAT之前判断记录数?这很困难。 所以,我们改变要求:每条简历记录显示为一行,每行以分号结束(包括最后一行)。这样我们就不需要去掉最后的符号,而是直接显示。那么用户看到的就是: 标题1:内容1; 标题2:内容2; 标题3:内容3; 这样也是可以接受的,因为最后一条后面也有分号,表示结束。所以我们可以这样写SQL: 假设我们使用MySQL,我们可以这样写: 查询每个人的所有简历信息,合并成一个字段: SELECT p.id, p.name, GROUP_CONCAT( CONCAT(r.resume_title, ':', r.content, ';\n') SEPARATOR '' -- 这里我们使用空字符串作为分隔符,因为我们在每条记录后面已经加了分号和换行符 ) AS resumes FROM person p LEFT JOIN resume r ON p.id = r.person_id GROUP BY p.id; 但是这样,每条记录后面都加了分号和换行符,然后用空字符串连接,实际上就是直接将所有字符串连接起来。但是这样会有一个问题:GROUP_CONCAT有长度限制(默认1024字节),如果简历内容很长或者很多,可能会被截断。 所以,我们需要先设置GROUP_CONCAT的最大长度: SET SESSION group_concat_max_len = 1000000; 然后执行上面的查询。 但是,如果简历内容特别多,我们可能需要考虑在应用层分步查询,或者不使用GROUP_CONCAT。 另一种方法:不在数据库层合并,而是在应用层循环处理。比如先查询个人基本信息,再查询这个人的所有简历记录,然后在应用层拼接字符串。 考虑到性能,如果一个人最多有几十条简历,那么使用应用层合并是可行的。 所以,我们有两种方案: 方案1:在数据库层使用GROUP_CONCAT合并,但要注意设置长度,并且每条记录后面都带分号和换行符(包括最后一条)。 方案2:在应用层合并。 这里我们分别给出两种方案的示例。 方案1(数据库层合并): 步骤: 1. 设置group_concat_max_len(根据实际情况设置) 2. 执行查询语句 示例SQL: SET SESSION group_concat_max_len = 1000000; -- 在同一个会话中设置 SELECT p.id, p.name, p.contact_info, GROUP_CONCAT( CONCAT(r.resume_title, ':', r.content, ';\n') ORDER BY r.created_at DESC -- 可以按照创建时间排序 SEPARATOR '' ) AS resumes FROM person p LEFT JOIN resume r ON p.id = r.person_id GROUP BY p.id; 这样,对于每个人,我们都会得到一个resumes字段,这个字段包含了这个人的所有简历记录,每条记录格式为:简历标题:简历内容;\n 例如: 张三的简历记录: Java开发工程师:5年Java经验...;\n 项目经理:PMP认证...;\n 在显示的时候,我们直接按照这个字符串显示,就会换行。 方案2(应用层合并): 步骤: 1. 查询个人基本信息 2. 查询这个人的所有简历记录 3. 遍历简历记录,拼接字符串:每条记录都拼接成“标题:内容;\n”的格式 示例代码(Node.js): async function getPersonResumes(personId) { const person = await db.query('SELECT * FROM person WHERE id = ?', [personId]); if (person.length === 0) { return null; } const resumes = await db.query('SELECT resume_title, content FROM resume WHERE person_id = ? ORDER BY created_at DESC', [personId]); let resumeText = ''; for (let resume of resumes) { resumeText += `${resume.resume_title}:${resume.content};\n`; } return { ...person[0], resumes: resumeText // 这里resumes是一个字符串,包含了所有简历记录 }; } 这样,我们就实现了在应用层合并。 两种方案比较: 方案1:只需要一次数据库查询,但是当简历内容非常大时,可能会影响性能(因为要拼接大字符串),而且GROUP_CONCAT有长度限制(虽然可以设置,但设置过大会占用较多内存)。 方案2:需要两次数据库查询,但是可以分页查询简历记录(如果简历记录非常多,我们可以分页,但是这里要求全部合并,所以一般不分页),而且不会受到长度限制,更灵活。 根据实际情况选择。 如果简历记录数量不多(比如每个人最多几十条),那么方案1更高效(一次查询)。如果简历记录可能非常多(比如上千条),那么建议在应用层处理,并且可以考虑分页显示,而不是一次性全部显示。 但是,用户要求是“合并为一条记录”,所以我们需要一次性全部获取。因此,如果记录数很多,建议使用应用层处理,并注意防止内存溢出。 另外,用户要求用分号分隔并换行显示,以上两种方案都满足。 总结:根据实际情况选择数据库层合并还是应用层合并。 下面,我们给出一个完整的SQL示例(方案1)和代码示例(方案2)。 注意:如果使用其他数据库,可能没有GROUP_CONCAT函数,那么可以使用其他方式,比如SQL Server的STRING_AGG(SQL Server 2017+)或PostgreSQL的string_agg。 例如在SQL Server 2017+中: SELECT p.id, p.name, p.contact_info, STRING_AGG( CONCAT(r.resume_title, ':', r.content, ';\n'), '') WITHIN GROUP (ORDER BY r.created_at DESC) AS resumes FROM person p LEFT JOIN resume r ON p.id = r.person_id GROUP BY p.id, p.name, p.contact_info; 在PostgreSQL中: SELECT p.id, p.name, p.contact_info, STRING_AGG( CONCAT(r.resume_title, ':', r.content, ';\n'), '' ORDER BY r.created_at DESC) AS resumes FROM person p LEFT JOIN resume r ON p.id = r.person_id GROUP BY p.id; 但是注意,在SQL Server和PostgreSQL中,CONCAT函数可以连接多个字符串,但是注意NULL值,如果某个字段为NULL,那么CONCAT的结果可能为NULL,所以最好用ISNULL或COALESCE处理。 由于我们这里主要使用MySQL,所以以上述MySQL示例为准。 最后,根据用户引用的内容,我们并没有直接相关的引用,但是引用[1]中提到了UNION,但这里我们不需要UNION,因为我们是同一个表的多行合并。引用[4]提到了HAVING,但这里我们不需要HAVING。引用[5]提到了SQL语法,我们遵循即可。 因此,我们给出两种方案,用户可以根据自己的数据库和实际情况选择。</think>### 解决方案:根据个人ID合并多条简历记录(分号分隔+换行显示) #### 方法1:MySQLGROUP_CONCAT 函数(推荐) ```sql SELECT p.id AS person_id, p.name, GROUP_CONCAT( CONCAT(r.resume_title, ':', r.content) SEPARATOR ';\n' -- 关键:用分号和换行符分隔 ) AS combined_resumes FROM person p LEFT JOIN resume r ON p.id = r.person_id GROUP BY p.id; ``` **效果示例**: ``` person_id | name | combined_resumes --------------------------------------------- 1001 | 张三 | Java开发:5年Spring经验;\n | | 项目经理:PMP认证;\n | | 技术总监:10年团队管理 ``` #### 方法2:应用层处理(Node.js/Python示例) ```javascript // Node.js 实现 async function mergeResumes(personId) { const resumes = await db.query(` SELECT resume_title, content FROM resume WHERE person_id = ? ORDER BY created_at DESC`, [personId]); return resumes.map(r => `${r.resume_title}:${r.content}` ).join(';\n'); // 分号+换行连接 } // 调用示例 const merged = await mergeResumes(1001); console.log(merged); ``` ```python # Python 实现 def merge_resumes(person_id): cursor.execute(""" SELECT resume_title, content FROM resume WHERE person_id = %s ORDER BY created_at DESC""", (person_id,)) return ';\n'.join( f"{row['resume_title']}:{row['content']}" for row in cursor.fetchall() ) ``` #### 方法3:SQL Server的STRING_AGG函数 ```sql SELECT p.id AS person_id, STRING_AGG( CONCAT(r.resume_title, ':', r.content), ';\n' -- 分号+换行符分隔 ) WITHIN GROUP (ORDER BY r.created_at DESC) AS combined_resumes FROM person p LEFT JOIN resume r ON p.id = r.person_id GROUP BY p.id; ``` ### 关键点说明 1. **分隔符控制**: - `SEPARATOR ';\n'` 实现分号分隔+换行 - 在SQL结果中显示为`;\n`,应用层渲染时会自动换行 2. **排序控制**(按时间倒序): ```sql GROUP_CONCAT(... ORDER BY created_at DESC SEPARATOR ';\n') ``` 3. **空值处理**: ```sql IFNULL(GROUP_CONCAT(...), '暂无简历') AS combined_resumes ``` 4. **长度限制解决**(MySQL): ```sql SET SESSION group_concat_max_len = 1000000; -- 调大限制 ``` ### 效果对比 | 方法 | 优点 | 缺点 | |---------------|-------------------------------|--------------------------| | GROUP_CONCAT | 单次查询完成,效率高 | MySQL特有,长度限制 | | 应用层处理 | 跨数据库通用,灵活控制格式 | 需要两次数据库访问 | | STRING_AGG | SQL Server原生支持,语法简洁 | 不适用于MySQL/PostgreSQL | > **推荐场景**: > - MySQL环境 → 方法1(GROUP_CONCAT)[^1] > - 多数据库兼容 → 方法2(应用层处理) > - 大数据量 → 方法2(避免GROUP_CONCAT长度限制) ### 示例输出 ```plaintext Java开发工程师:精通Spring Cloud微服务架构;\n 项目经理:主导过百万级用户项目,PMP认证;\n 技术总监:10年技术团队管理经验 ``` 渲染后显示: ``` Java开发工程师:精通Spring Cloud微服务架构; 项目经理:主导过百万级用户项目,PMP认证; 技术总监:10年技术团队管理经验 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值