DEBUG.WIKI
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
雨云;给项目一台云服务器
RackNerd;$21.99/年 · 1GB KVM

SQL Server LEN 不计尾部空格,DATALENGTH 又返回字节数

区分 LEN 的字符计数与 DATALENGTH 的字节数;需要保留尾部空格计数时,使用查询内标记并避免有限长度拼接截断。

SQL Server 字符串已经补了尾部空格,LEN() 却仍返回补齐前的长度。例如 N'ABC ' 有两个末尾空格,但 LEN() 返回 3。这不表示空格没有存进去:LEN() 的计数会排除尾部空格。

DATALENGTH() 会包含这些空格占用的空间,但返回单位是字节,不是字符。需要检查“固定十个字符”时,不能不看数据类型就把 LEN 全部替换成 DATALENGTH。

用同一份值比较两种计数

下面是只读变量示例,不修改表:

DECLARE @value nvarchar(20) = N'ABC  ';

SELECT LEN(@value) AS length_without_trailing_spaces,
       DATALENGTH(@value) AS stored_bytes;

这份输入的结果应分别为 3 和 10。第二项包含五个 BMP 字符在 nvarchar 中占用的字节。把它当成“十个字符”,后续填充或校验就会算错。

LEN() 不会去掉字符串中间或开头的普通空格,也不是统计界面上看起来有多宽。若真正的问题是入库时内容被截断、应用主动 trim,或查询工具显示不出尾空格,应分别检查写入值和读取值,不能单凭一个 LEN 结果判断。

要计入末尾空格,可在表达式末尾加标记

如果需求是按 LEN 的字符计数规则统计,并把尾部空格也计入,可以先扩为 max 类型,再追加一个非空格标记:

DECLARE @value nvarchar(20) = N'ABC  ';

SELECT CASE
           WHEN @value IS NULL THEN NULL
           ELSE LEN(CONVERT(nvarchar(max), @value) + N'#') - 1
       END AS length_including_trailing_spaces;

加上 # 后,原来的末尾空格位于字符串内部,LEN 会计入它们;最后减掉追加标记的一个字符。上述输入应得到 5,空字符串应得到 0,NULL 仍保留为 NULL。标记只存在于查询表达式中,不写回原字段,也不要求原字符串里不能出现 #。

先转换为 nvarchar(max) 是为了避免追加标记时发生有限长度字符串截断。直接对已经接近容量上限的 nvarchar(4000) 或 varchar(8000) 追加字符,标记可能没有完整保留,此时再减一就会算错。转换要发生在拼接之前;把已经截断的拼接结果转成 max,不能找回丢失内容。

表字段的只读形式可以写成:

SELECT record_id,
       CASE
           WHEN text_value IS NULL THEN NULL
           ELSE LEN(CONVERT(nvarchar(max), text_value) + N'#') - 1
       END AS character_count
FROM dbo.ImportRows;

表名和列名都是占位值,实际查询要保留原有范围限制。这里是在统计转换后的 Unicode 表达式,不是获取原列占用字节。如果目标是保留 varchar 原有类型和排序规则语义,应按实际类型设计表达式,不要把转换当成无条件通用的数据迁移。

字节数除以二也不是通用字符数

DATALENGTH() 适合检查表达式占用了多少字节。Unicode 字符串中,补充平面字符可能由一对 UTF-16 代理项组成;采用 SC 排序规则时,LEN 会把这一对计为一个字符,而字节数除以二得到的是另一种单位。

varchar 的字节数同样取决于编码和排序规则,不能默认所有字符都只占一个字节。含中文、补充字符等数据时,先明确业务要求是字节、数据库的字符计数,还是用户看到的字形数量。

校验长度,不要先改原数据

先在受控数据中比较无空格、尾空格、全空格、空字符串、NULL、接近列容量的文本和补充字符。确认计数单位后,再修改填充或校验逻辑;不要为了让 LEN 达到期望数字直接 trim 数据,也不要给业务字段永久追加标记。

这项调整通常只需要修改查询表达式。保留原查询以便回退;若旧长度计算已生成固定宽度文件或更新其他列,还需单独检查这些产物,改回 SQL 不会自动重建它们。