在数据库开发中,把多个字段的值连接成一个字符串是最常见的需求之一,比如把姓和名拼成完整姓名,把省市区拼成详细地址,或者动态生成一批 UPDATE 语句。但很多初学者第一次写拼接 SQL 时都会踩坑:在 MySQL 里用加号发现结果变成了数字相加,在 SQL Server 里用 CONCAT 又提示函数不存在,遇到 NULL 值整个结果直接变成空白。这些问题的根源在于不同数据库对字符串拼接的支持方式并不统一,只有理解每种写法的原理和边界情况,才能写出可靠的拼接语句。

一、三大主流拼接方式:CONCAT、|| 与 +
SQL 标准定义的字符串拼接函数是 CONCAT,几乎所有主流数据库都支持它,语法也非常直观:把要拼接的参数依次传入即可。MySQL 从 4.1 版本开始提供 CONCAT,SQL Server 从 2012 版本引入,PostgreSQL 和 Oracle 同样支持。基本写法如下:
SELECT CONCAT(last_name, ' ', first_name) AS full_name FROM employees; -- 输出类似:张 三
除了函数形式,SQL 标准还定义了双竖线 || 作为拼接操作符,Oracle 和 PostgreSQL 原生支持这种写法。它的优点是写起来简洁,多个字段连着写很长一串也不用嵌套函数:
SELECT last_name || first_name AS full_name,
province || city || district AS address
FROM users;
而 SQL Server 走了另一条路,它用加号 + 做字符串拼接,这也是 T-SQL 的特色写法。需要注意加号同时也是数字运算符,一旦某个参数是数字类型,SQL Server 会优先按加法处理,这时候就需要显式转换类型:
-- SQL Server 写法 SELECT last_name + ' ' + first_name AS full_name FROM employees; -- 若 age 是 int 类型,直接拼接会报错,需要转换 SELECT name + ',年龄:' + CAST(age AS VARCHAR(10)) AS info FROM users;
MySQL 的行为正好相反,加号在 MySQL 中只做数学运算,字符串和数字用 + 连接时会先转成数字再相加,转不成数字就按 0 处理。这就是为什么在 MySQL 里写 'abc' + 'def' 得到的是 0 而不是 abcdef。所以同一份 SQL 要跨库执行时,拼接写法必须针对性调整,最稳妥的通用方案是统一用 CONCAT 函数。
二、NULL 值处理:最容易踩的坑
拼接操作最隐蔽的陷阱来自 NULL。在 MySQL 的 CONCAT 中,只要任何一个参数为 NULL,整个结果就是 NULL,这在业务上往往不符合预期,比如备注字段为空就导致整条地址信息都显示不出来:
SELECT CONCAT('北京市', NULL, '朝阳区'); -- 结果为 NULL
解决办法有两种:一种是用 IFNULL 或 COALESCE 把 NULL 替换成空字符串;另一种是直接使用 CONCAT_WS 函数,它会自动忽略 NULL 参数,第一个参数是分隔符:
-- 方式一:手动处理 NULL
SELECT CONCAT('北京市', IFNULL(district, ''), '朝阳区') FROM areas;
-- 方式二:CONCAT_WS 自动跳过 NULL
SELECT CONCAT_WS('-', province, city, district) AS region FROM areas;
-- NULL 的字段会被直接跳过,不会污染整体结果
SQL Server 的 CONCAT 在这方面反而更省心,它会自动把 NULL 当作空字符串处理,所以从 Oracle 迁移到 SQL Server 时经常会出现行为差异。Oracle 的 || 拼接遇到 NULL 也会当作空字符串处理,这是 Oracle 的特例行为,与 SQL 标准的定义并不完全一致,跨库迁移时要特别留意。PostgreSQL 的 || 遇到 NULL 则返回 NULL,符合标准行为,处理方式可以用 COALESCE:
-- PostgreSQL 中处理 NULL SELECT COALESCE(province, '') || COALESCE(city, '') FROM areas;
三、进阶函数:分组拼接与分隔符拼接
实际业务中有一类需求是按分组把多行数据拼成一个字符串,典型场景如把一个部门所有员工姓名合并显示、把订单的多个商品标签汇总。MySQL 提供了 GROUP_CONCAT,PostgreSQL 和 SQL Server 2017 之后提供 STRING_AGG,用法对比如下:
-- MySQL:按部门汇总员工姓名
SELECT dept_id,
GROUP_CONCAT(emp_name ORDER BY emp_name SEPARATOR ',') AS members
FROM employees
GROUP BY dept_id;
-- PostgreSQL:STRING_AGG
SELECT dept_id,
STRING_AGG(emp_name, ',' ORDER BY emp_name) AS members
FROM employees
GROUP BY dept_id;
-- SQL Server 2017+
SELECT dept_id,
STRING_AGG(emp_name, ',') AS members
FROM employees
GROUP BY dept_id;
使用 GROUP_CONCAT 有一个常见问题:默认长度上限是 1024 字节,超过会被静默截断,生产环境中经常因此丢数据。解决办法是调整参数:
SET SESSION group_concat_max_len = 102400;
Oracle 老版本没有内置分组拼接函数,经典做法是使用 LISTAGG 分析函数,如果版本低于 11g 则需要借助 WM_CONCAT 或自定义聚合函数。另外 SQL Server 老版本常用 FOR XML PATH 的曲线方案实现,虽然能解决问题,但可读性较差,升级到 2017 之后建议直接换成 STRING_AGG。
四、实战场景与性能注意事项
拼接函数在动态 SQL 生成场景中用得非常多。比如需要根据配置表批量生成一批修复数据的 UPDATE 语句,可以用拼接快速产出脚本文件,导出后直接执行:
SELECT CONCAT(
'UPDATE orders SET status = ''', target_status,
''' WHERE order_no = ''', order_no, ''';'
) AS fix_sql
FROM order_fix_list;
关于性能需要说明几点。首先,在大表上对字段做拼接再用于 WHERE 条件过滤,会导致索引失效,正确做法是把条件改写成可以对索引列直接比较的形式。其次,LIKE 模糊查询中以通配符开头的写法本身就无法走索引,这与拼接无关,但两者经常一起出现,容易被误判为拼接拖慢了查询。最后,在 SELECT 列表中拼接字段的开销通常很小,只有涉及大量函数嵌套或超长文本时才需要关注。
总结一下选择思路:优先使用各数据库的标准函数 CONCAT,涉及 NULL 时用 CONCAT_WS 或 COALESCE 兜底,分组聚合场景按数据库选择 GROUP_CONCAT 或 STRING_AGG,写跨库兼容的 SQL 时避免依赖 || 和 + 这类方言特性。掌握这些规则后,字符串拼接就不再是SQL 开发中的绊脚石了。