字符串截取乱码?都是编码惹的祸!
引言
有小伙伴在使用substring截取中文字符串时,发现返回的是乱码,就很困惑;看个例子,下图SQL的本意是从第五个字符开始截取两个字符,但事与愿违~
select substring('xian华为666', 5, 2);
这种问题不止一个小伙伴有在实践中遇到过,因此带大家一起剖析下问题所在。
截取函数
GaussDB(DWS)中截取字符串的函数主要有三个:substrb()、substr()、substring() ,以及他们截取单位的差异,不了解的小伙伴可以看下这个帖子:GaussDB(DWS)中的字符截取三胞胎【这次高斯不是数学家】。
说回上面的案例,我们知道 substring 是按字符截取,但为什么会输出乱码?
数据库编码
场景1:服务端SQL_ASCII + 客户端UTF8
这是因为数据库的编码原因,在编码为SQL_ASCII的数据库中,数据是不区分编码,按字节流存的。因此 substring(‘xian华为666’, 5, 2) 所代表的含义是从第五个字节开始截取2个字节的长度。而实际截取到的结果字节,与原始字符编码有关。
在UTF8编码中,中文为三个字节,“华为”表示为E58D8E E4B8BA,最终截取结果为两个字节,因此不能正常表示一个中文字符。
-- UTF8编码的原始字符的16进制表示如下
postgres=# select hex('xian华为666');
hex
----------------------------
7869616EE58D8EE4B8BA363636
(1 row)
--因此实际截取到的是两个字节,而不是两个字符
postgres=# select hex(substring('xian华为666', 5, 2));
hex
------
E58D
(1 row)
场景2:服务端SQL_ASCII + 客户端GBK
在GBK编码中,中文为两个字节,“华为”表示为BBAA CEAA,最终截取结果为两个字节,刚好可以表示一个中文字符“华”。
-- GBK编码的原始字符的16进制表示如下
postgres=# select hex('xian华为666');
hex
-------------------------
7869616EBBAACEAA363636
(1 row)
--因此实际截取到的是两个字节,而不是两个字符
postgres=# select hex(substring('xian华为666', 5, 2));
hex
------
BBAA
(1 row)
场景3:服务端UTF8 + 客户端UTF8
服务端指定为UTF8时,会根据UTF8编码区分字符,所以substring表现为字符的截取。最终截取结果为两个字符“华为”。
--因此实际截取到的是两个字符,也就是6个字节
postgres=# select hex(substring('xian华为666', 5, 2));
hex
--------------
E58D8EE4B8BA
(1 row)
场景4:服务端UTF8 + 客户端GBK
只要服务端指定了具体的编码如UTF8时,即使客户端编码与服务端编码不一致,在服务端会在数据处理前统一转换为当前编码即UTF8,因此最终截取结果仍为两个字符“华为”,与场景3保持一致。
--因此实际截取到的是两个字符,仍然是6个字节
postgres=# select hex(substring('xian华为666', 5, 2));
hex
--------------
E58D8EE4B8BA
(1 row)
知识小结
通过以上一番解析之后,相信大家对字符截取的异常结果的处理就可以手到擒来了。
回过头来看下,文章开头的乱码问题应该怎么处理呢?
1,数据库编码SQL_ASCII不变,修改截取的长度为需要的字节数长度,此时注意区分源数据编码。
-- 源数据为UTF8编码
postgres=# select substring('xian华为666', 5, 6);
substring
-----------
华为
(1 row)
-- 源数据为GBK编码
postgres=# select substring('xian华为666', 5, 4);
substring
-----------
华为
(1 row)
2,切换到编码为UTF8的数据库,即可按照字符数截取。
-- 按照字符个数截取
postgres=# select substring('xian华为666', 5, 2);
substring
-----------
华为
(1 row)
想了解GuassDB(DWS)更多信息,欢迎微信搜索“GaussDB DWS”关注微信公众号,和您分享最新最全的PB级数仓黑科技~
- 点赞
- 收藏
- 关注作者
评论(0)