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

本文涉及的产品
云原生数据库 PolarDB MySQL 版,Serverless 5000PCU 100GB
简介: 标签 PostgreSQL , conversion , pg_conversion , 拼音 , 编码转换 , convert , convert_to , convert_from 背景 国内的应用,在文本排序上基本都是按照拼音来进行排序的。 在不同的字符集中,汉字的编码可能不一样,比如UTF8和GBK,其中GBK是按拼音的顺序进行编码的,而UTF8则不是。 所以如果你的数据库使

标签

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数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
相关文章
|
2月前
|
安全 关系型数据库 MySQL
总结MySQL 的一些知识点:MySQL 排序(上)
总结MySQL 的一些知识点:MySQL 排序
|
3月前
|
关系型数据库 PostgreSQL
PostgreSQL排序字段不唯一导致分页查询结果出现重复数据
PostgreSQL排序字段不唯一导致分页查询结果出现重复数据
41 0
|
4月前
|
SQL 前端开发 关系型数据库
MYSQL 排序和分组怎么做?
MYSQL 排序和分组怎么做?
41 1
|
21天前
|
缓存 关系型数据库 MySQL
MySQL查询优化:提速查询效率的13大秘籍(合理使用索引合并、优化配置参数、使用分区优化性能、避免不必要的排序和group by操作)(下)
MySQL查询优化:提速查询效率的13大秘籍(合理使用索引合并、优化配置参数、使用分区优化性能、避免不必要的排序和group by操作)(下)
|
1月前
|
存储 人工智能 关系型数据库
详细介绍TiDB 与 MySQL 中的常用字符集及排序规则
一文理清 TiDB 与 MySQL 中的常用字符集及排序规则
122 6
|
7天前
|
SQL 关系型数据库 MySQL
排序与分页——“MySQL数据库”
排序与分页——“MySQL数据库”
|
16天前
|
SQL 关系型数据库 MySQL
mysql基本查询、运算符、排序和分页
mysql基本查询、运算符、排序和分页
|
27天前
|
关系型数据库 MySQL 开发工具
MySQL分组后,组内排序,然后取每组的第一条数据
MySQL分组后,组内排序,然后取每组的第一条数据
15 1
|
1月前
|
关系型数据库 MySQL
Mysql基础第八天,排序检索数据
Mysql基础第八天,排序检索数据
24 1
|
2月前
|
存储 关系型数据库 MySQL
【2024】新建mysql数据库,如何选择字符集和排序规则
【2024】新建mysql数据库,如何选择字符集和排序规则
156 1