PostgreSQL 按拼音排序 - convert to GBK/EUC_CN coding

本文涉及的产品
云原生多模数据库 Lindorm,多引擎 多规格 0-4节点
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
云数据库 MongoDB,通用型 2核4GB
简介:

标签

PostgreSQL , conversion , pg_conversion , 拼音 , 编码转换 , convert , convert_to , convert_from


背景

国内的应用,在文本排序上基本都是按照拼音来进行排序的。

在不同的字符集中,汉字的编码可能不一样,比如UTF8和GBK,其中GBK是按拼音的顺序进行编码的,而UTF8则不是。

所以如果你的数据库使用了UTF8编码,对中文字段进行排序时,可能得到的并不是按拼音排序的结果。

在PostgreSQL中,中文按拼音排序的编码包括GB18030, EUC_CN, GBK, BIG5, EUC_TW 等。

为了得到拼音排序,可以使用编码转换后的值来排序,索引也可以使用编码转换的表达式索引。

编码

PostgreSQL支持的编码如下

https://www.postgresql.org/docs/9.6/static/multibyte.html

PostgreSQL Character Sets

Name Description Language Server? Bytes/Char Aliases
BIG5 Big Five Traditional Chinese No 1-2 WIN950, Windows950
EUC_CN Extended UNIX Code-CN Simplified Chinese Yes 1-3 -
EUC_JP Extended UNIX Code-JP Japanese Yes 1-3 -
EUC_JIS_2004 Extended UNIX Code-JP, JIS X 0213 Japanese Yes 1-3 -
EUC_KR Extended UNIX Code-KR Korean Yes 1-3 -
EUC_TW Extended UNIX Code-TW Traditional Chinese, Taiwanese Yes 1-3 -
GB18030 National Standard Chinese No 1-4 -
GBK Extended National Standard Simplified Chinese No 1-2 WIN936, Windows936
ISO_8859_5 ISO 8859-5, ECMA 113 Latin/Cyrillic Yes 1 -
ISO_8859_6 ISO 8859-6, ECMA 114 Latin/Arabic Yes 1 -
ISO_8859_7 ISO 8859-7, ECMA 118 Latin/Greek Yes 1 -
ISO_8859_8 ISO 8859-8, ECMA 121 Latin/Hebrew Yes 1 -
JOHAB JOHAB Korean (Hangul) No 1-3 -
KOI8R KOI8-R Cyrillic (Russian) Yes 1 KOI8
KOI8U KOI8-U Cyrillic (Ukrainian) Yes 1 -
LATIN1 ISO 8859-1, ECMA 94 Western European Yes 1 ISO88591
LATIN2 ISO 8859-2, ECMA 94 Central European Yes 1 ISO88592
LATIN3 ISO 8859-3, ECMA 94 South European Yes 1 ISO88593
LATIN4 ISO 8859-4, ECMA 94 North European Yes 1 ISO88594
LATIN5 ISO 8859-9, ECMA 128 Turkish Yes 1 ISO88599
LATIN6 ISO 8859-10, ECMA 144 Nordic Yes 1 ISO885910
LATIN7 ISO 8859-13 Baltic Yes 1 ISO885913
LATIN8 ISO 8859-14 Celtic Yes 1 ISO885914
LATIN9 ISO 8859-15 LATIN1 with Euro and accents Yes 1 ISO885915
LATIN10 ISO 8859-16, ASRO SR 14111 Romanian Yes 1 ISO885916
MULE_INTERNAL Mule internal code Multilingual Emacs Yes 1-4
SJIS Shift JIS Japanese No 1-2 Mskanji, ShiftJIS, WIN932, Windows932
SHIFT_JIS_2004 Shift JIS, JIS X 0213 Japanese No 1-2 -
SQL_ASCII unspecified (see text) any Yes 1 -
UHC Unified Hangul Code Korean No 1-2 WIN949, Windows949
UTF8 Unicode, 8-bit all Yes 1-4 Unicode
WIN866 Windows CP866 Cyrillic Yes 1 ALT
WIN874 Windows CP874 Thai Yes 1 -
WIN1250 Windows CP1250 Central European Yes 1 -
WIN1251 Windows CP1251 Cyrillic Yes 1 WIN
WIN1252 Windows CP1252 Western European Yes 1 -
WIN1253 Windows CP1253 Greek Yes 1 -
WIN1254 Windows CP1254 Turkish Yes 1 -
WIN1255 Windows CP1255 Hebrew Yes 1 -
WIN1256 Windows CP1256 Arabic Yes 1 -
WIN1257 Windows CP1257 Baltic Yes 1 -
WIN1258 Windows CP1258 Vietnamese Yes 1 ABC, TCVN, TCVN5712, VSCII

与中文编码排序相关的包括 GB18030, EUC_CN, GBK, BIG5, EUC_TW

简体常用的包括GBK, EUC_CN。

编码转换

在PostgreSQL中,如果要将字符从一个编码转换为另一个编码,需要告诉数据库(create conversion)怎么转换(使用什么C函数),PG内置了一些转换的C函数和转换方法。

https://www.postgresql.org/docs/9.6/static/catalog-pg-conversion.html

pg_conversion

Name Type References Description
oid oid - Row identifier (hidden attribute; must be explicitly selected)
conname name - Conversion name (unique within a namespace)
connamespace oid pg_namespace.oid The OID of the namespace that contains this conversion
conowner oid pg_authid.oid Owner of the conversion
conforencoding int4 - Source encoding ID
contoencoding int4 - Destination encoding ID
conproc regproc pg_proc.oid Conversion procedure
condefault bool - True if this is the default conversion

查看内置的转换方法

可以看到utf8转换为中文编码的都支持了

postgres=> select * from pg_conversion where conname ~* 'gbk|gb18|euc_cn|euc_tw|big5' order by 1;
     conname     | connamespace | conowner | conforencoding | contoencoding |     conproc     | condefault 
-----------------+--------------+----------+----------------+---------------+-----------------+------------
 big5_to_euc_tw  |           11 |       10 |             36 |             4 | big5_to_euc_tw  | t
 big5_to_mic     |           11 |       10 |             36 |             7 | big5_to_mic     | t
 big5_to_utf8    |           11 |       10 |             36 |             6 | big5_to_utf8    | t
 euc_cn_to_mic   |           11 |       10 |              2 |             7 | euc_cn_to_mic   | t
 euc_cn_to_utf8  |           11 |       10 |              2 |             6 | euc_cn_to_utf8  | t
 euc_tw_to_big5  |           11 |       10 |              4 |            36 | euc_tw_to_big5  | t
 euc_tw_to_mic   |           11 |       10 |              4 |             7 | euc_tw_to_mic   | t
 euc_tw_to_utf8  |           11 |       10 |              4 |             6 | euc_tw_to_utf8  | t
 gb18030_to_utf8 |           11 |       10 |             39 |             6 | gb18030_to_utf8 | t
 gbk_to_utf8     |           11 |       10 |             37 |             6 | gbk_to_utf8     | t
 mic_to_big5     |           11 |       10 |              7 |            36 | mic_to_big5     | t
 mic_to_euc_cn   |           11 |       10 |              7 |             2 | mic_to_euc_cn   | t
 mic_to_euc_tw   |           11 |       10 |              7 |             4 | mic_to_euc_tw   | t
 utf8_to_big5    |           11 |       10 |              6 |            36 | utf8_to_big5    | t
 utf8_to_euc_cn  |           11 |       10 |              6 |             2 | utf8_to_euc_cn  | t
 utf8_to_euc_tw  |           11 |       10 |              6 |             4 | utf8_to_euc_tw  | t
 utf8_to_gb18030 |           11 |       10 |              6 |            39 | utf8_to_gb18030 | t
 utf8_to_gbk     |           11 |       10 |              6 |            37 | utf8_to_gbk     | t
(18 rows)

编码转换函数

注意数据库版本

PostgreSQL 8.x(如Greenplum), 将字符串从原编码转换为指定编码的字符串返回。

可能存在显示的问题。

                              List of functions
   Schema   |     Name      | Result data type | Argument data types |  Type  
------------+---------------+------------------+---------------------+--------
 pg_catalog | convert       | text             | text, name          | normal
 pg_catalog | convert       | text             | text, name, name    | normal

PostgreSQL 9.x, 将源编码字符串的字节流转换为指定编码的字符串的字节流返回。

避免了显示的问题。

                              List of functions
   Schema   |     Name     | Result data type | Argument data types |  Type  
------------+--------------+------------------+---------------------+--------
 pg_catalog | convert      | bytea            | bytea, name, name   | normal
 pg_catalog | convert_from | text             | bytea, name         | normal
 pg_catalog | convert_to   | bytea            | text, name          | normal

如果8.x需要避免显示问题,返回字节流,可以这样使用,推荐使用。

byteain(textout(convert(字符,'源编码','目标编码')))  

例子

当前数据库编码为UTF-8,中文排序未按拼音排序。

postgres=# \l
                             List of databases
   Name    |  Owner   | Encoding | Collate | Ctype |   Access privileges   
-----------+----------+----------+---------+-------+-----------------------
 db0       | postgres | UTF8     | C       | C     | 
 postgres  | postgres | UTF8     | C       | C     | 
 template0 | postgres | UTF8     | C       | C     | =c/postgres          +
           |          |          |         |       | postgres=CTc/postgres
 template1 | postgres | UTF8     | C       | C     | =c/postgres          +
           |          |          |         |       | postgres=CTc/postgres
(4 rows)

postgres=# select * from (values ('刘德华'), ('刘少奇')) t(id) order by id;
   id   
--------
 刘少奇
 刘德华
(2 rows)

按拼音排序方法(目标可以改成EUC_CN)

8.x

postgres=> select * from (values ('刘德华'), ('刘少奇')) t(id) order by byteain(textout(convert(id,'UTF-8','GBK')));
   id   
--------
 刘德华
 刘少奇
(2 rows)


9.x
postgres=# select * from (values ('刘德华'), ('刘少奇')) t(id) order by convert(id::bytea,'UTF-8','GBK');
   id   
--------
 刘德华
 刘少奇
(2 rows)

注意多音字

中文有一些多音字,比如重庆(chongqing), 但是编码时它可能是按zhong编码的,所以看这个例子。

postgres=> select * from (values ('中山'), ('重庆')) t(id) order by byteain(textout(convert(id,'UTF-8','GBK')));
  id  
------
 中山
 重庆
(2 rows)

索引

表达式索引即可,使用immutable function.

代码

8.x

postgres=> \df+ convert
                                                                                        List of functions
   Schema   |  Name   | Result data type | Argument data types |  Type  | Data access | Volatility |   Owner   | Language | Source code |                       Description                       
------------+---------+------------------+---------------------+--------+-------------+------------+-----------+----------+-------------+---------------------------------------------------------
 pg_catalog | convert | text             | text, name          | normal | no sql      | stable     | xxx | internal | pg_convert  | convert string with specified destination encoding name
 pg_catalog | convert | text             | text, name, name    | normal | no sql      | stable     | xxx | internal | pg_convert2 | convert string with specified encoding names
(2 rows)


9.x

postgres=# \df+ convert
                                                                                                List of functions
   Schema   |  Name   | Result data type | Argument data types |  Type  | Volatility | Parallel |  Owner   | Security | Access privileges | Language | Source code |                 Description                  
------------+---------+------------------+---------------------+--------+------------+----------+----------+----------+-------------------+----------+-------------+----------------------------------------------
 pg_catalog | convert | bytea            | bytea, name, name   | normal | stable     | safe     | postgres | invoker  |                   | internal | pg_convert  | convert string with specified encoding names
(1 row)

src/backend/utils/mb/mbutils.c

/*
 * Convert string between two arbitrary encodings.
 *
 * BYTEA convert(BYTEA string, NAME src_encoding_name, NAME dest_encoding_name)
 */
Datum
pg_convert(PG_FUNCTION_ARGS)
{
        bytea      *string = PG_GETARG_BYTEA_PP(0);
        char       *src_encoding_name = NameStr(*PG_GETARG_NAME(1));
        int                     src_encoding = pg_char_to_encoding(src_encoding_name);
        char       *dest_encoding_name = NameStr(*PG_GETARG_NAME(2));
        int                     dest_encoding = pg_char_to_encoding(dest_encoding_name);
        const char *src_str;
        char       *dest_str;
        bytea      *retval;
        int                     len;

        if (src_encoding < 0)
                ereport(ERROR,
                                (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                                 errmsg("invalid source encoding name \"%s\"",
                                                src_encoding_name)));
        if (dest_encoding < 0)
                ereport(ERROR,
                                (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                                 errmsg("invalid destination encoding name \"%s\"",
                                                dest_encoding_name)));

        /* make sure that source string is valid */
        len = VARSIZE_ANY_EXHDR(string);
        src_str = VARDATA_ANY(string);
        pg_verify_mbstr_len(src_encoding, src_str, len, false);

        /* perform conversion */
        dest_str = (char *) pg_do_encoding_conversion((unsigned char *) src_str,
                                                                                                  len,
                                                                                                  src_encoding,
                                                                                                  dest_encoding);

        /* update len if conversion actually happened */
        if (dest_str != src_str)
                len = strlen(dest_str);

        /*
         * build bytea data type structure.
         */
        retval = (bytea *) palloc(len + VARHDRSZ);
        SET_VARSIZE(retval, len + VARHDRSZ);
        memcpy(VARDATA(retval), dest_str, len);

        if (dest_str != src_str)
                pfree(dest_str);

        /* free memory if allocated by the toaster */
        PG_FREE_IF_COPY(string, 0);

        PG_RETURN_BYTEA_P(retval);
}

参考

1. https://www.postgresql.org/docs/9.6/static/multibyte.html#AEN39011

2. https://www.postgresql.org/docs/9.6/static/sql-createconversion.html

3. https://www.postgresql.org/docs/9.6/static/catalog-pg-conversion.html

相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
相关文章
|
3月前
|
存储 自然语言处理 关系型数据库
👨‍💻如何使用MySQL存储Emoji表情,UTF-8和UTF-8MB4字符编码有何区别?
👨‍💻如何使用MySQL存储Emoji表情,UTF-8和UTF-8MB4字符编码有何区别?
93 1
|
关系型数据库 MySQL
MySQL的校对规则设置为 utf8mb4_unicode_ci是干什么的?底层原理是什么?
MySQL的校对规则设置为 utf8mb4_unicode_ci是干什么的?底层原理是什么?
132 0
|
关系型数据库 MySQL
MySQL字符类型排序规则COLLATE
MySQL字符类型排序规则COLLATE
|
存储 关系型数据库 MySQL
DataX:导入4字节UTF8编码(生僻字)到Mysql数据库的utf8mb4数据表
MySql数据库的编码支持UFT8字符集。utf-8编码可能是2个字节、3个字节、4个字节的字符,MYSQL的utf-8编码,只支持3个字节的字符。汉字中很多生僻字都是4个字节的字符,日常生活中人的姓名就会有很多高位的生僻字。
5387 0
|
编解码 JSON 数据格式
Python常见问题 - requests请求参数包含中文报错:UnicodeEncodeError: 'latin-1' codec can't encode characters in position 13-14: 小明 is not valid Latin-1. Use body.encode('utf-8')
Python常见问题 - requests请求参数包含中文报错:UnicodeEncodeError: 'latin-1' codec can't encode characters in position 13-14: 小明 is not valid Latin-1. Use body.encode('utf-8')
1643 0
|
SQL 关系型数据库 MySQL
MySql 8.0 字符编码utf8mb4小实验
MySql 8.0 字符编码utf8mb4小实验
867 0
|
存储 架构师 关系型数据库
总结:被MySQL UTF8编码坑的惨痛教训...
最近遇到几个项目被MySQL的utf8编码坑,想起之前编码问题被坑的惨痛教训,记录一下,警示自己。曾几何时,每次建库都选utf8,觉得自己比那些用乱七八糟编码的人不知道酷到哪里去了。
|
SQL 关系型数据库 数据库
PostgreSQL sql文件编码引起的数据导入乱码或查询字符集异常报错(invalid byte sequence)
标签 PostgreSQL , 乱码 , 文件编码 背景 当用户客户端字符集与服务端字符集不匹配时,写入的多字节字符(例如中文)可能出现乱码。 例子 数据库字符集为sql_ascii,允许存储任意编码字符。
3356 0
|
JavaScript 关系型数据库 MySQL
mysql中utf8 ,utf8mb4区别转化方法
mysql utf8 utf8mb4的区别及转化方法
11686 0