场景
对 pg 数据库中的行政区划名称添加中文首拼
实现
在线找 pg 数据库函数实现。比如“PostgreSQL数据库sql查询如何获取汉字拼音首字母(好多类似文章实现)”,使用后发现如下问题
- 非所有汉字都可以转换。比如“岐山县”、“濮阳市”、“漾濞彝族自治县”这些生僻字就无法转换
- 无法区分多音字。比如“重庆市cqs”、“重阳镇cyz”、“重石乡zsx”,重都会识别为 z,而非 c
后改为用 python 的 “pypinyin” 库(官方文档)实现,该库可以实现获取中文全拼、首拼、音调等。先从 pg 库中读取汉字,转换出首拼后更新回数据库。因为是离线字典表,没有实时转换的要求,T+1 转换下即可。
import psycopg2
import pypinyin as py
def get_first_pinyin(name):
return ''.join(py.lazy_pinyin(name, style=py.Style.FIRST_LETTER))
def update_first_pinyin(conn):
try:
with conn.cursor() as cur:
# 读取数据
cur.execute("SELECT area_id, area_name FROM dim.area_name_piny")
rows = cur.fetchall()
# 更新首拼
for row in rows:
area_id, area_name = row
first_py = get_first_pinyin(area_name)
cur.execute(
"UPDATE dim.area_name_piny SET firstpy = %s WHERE area_id = %s",
(first_py, area_id)
)
conn.commit()
except Exception as e:
print(f"An error occurred: {e}")
conn.rollback()
if __name__ == '__main__':
# 数据库连接配置
conn_params = {
'dbname': 'xx',
'user': 'xx',
'password': 'xx',
'host': 'xx',
'port': 'xx'
}
try:
# 建立数据库连接
conn = psycopg2.connect(**conn_params)
update_first_pinyin(conn)
finally:
conn.close()
实现原理:维护了一张包含汉字和对应拼音的字典(通常是从公共的汉字拼音表中提取)。这个字典中存储了大量汉字及其拼音信息,以支持拼音转换。前人都维护好了,拿来主义直接使用。
解惑(备忘,可不用看)
为什么找到的 pg 数据库函数无法正确转换?原因是:函数中缺乏正确的判断逻辑或拼音字典。
比如:汉字“濮”,根据 GB18030 编码,汉字“濮”的字节表示为 0xE5 0xA7。 E5A7对应的十进制为 58791。不在下面的判断字母范围内,导致无法转换。
下面贴一下无法全部转换的 pg 数据库函数的代码。
CREATE OR REPLACE FUNCTION cnfirstchar(s CHARACTER VARYING)
RETURNS CHARACTER VARYING
IMMUTABLE
LANGUAGE plpgsql
AS
$$
DECLARE
retval CHARACTER VARYING := '';
c CHARACTER VARYING;
l INTEGER := LENGTH(s);
b BYTEA;
w INTEGER;
BEGIN
WHILE l > 0 LOOP
-- 获取第一个字符
c := LEFT(s, 1);
-- 将字符转换为GB18030编码的字节序列
b := CONVERT_TO(c, 'GB18030')::BYTEA;
-- 检查是否为ASCII字符
IF GET_BYTE(b, 0) < 127 THEN
retval := retval || UPPER(c);
ELSIF LENGTH(b) = 2 THEN
-- 获取汉字的编码值
w := GET_BYTE(b, 0) * 256 + GET_BYTE(b, 1);
-- 根据对应的区间判断拼音首字母
IF w BETWEEN 45217 AND 45252 THEN
retval := retval || 'a';
ELSIF w BETWEEN 45253 AND 45760 THEN
retval := retval || 'b';
ELSIF w BETWEEN 45761 AND 46317 THEN
retval := retval || 'c';
ELSIF w BETWEEN 46318 AND 46825 THEN
retval := retval || 'd';
ELSIF w BETWEEN 46826 AND 47009 THEN
retval := retval || 'e';
ELSIF w BETWEEN 47010 AND 47296 THEN
retval := retval || 'f';
ELSIF w BETWEEN 47297 AND 47613 THEN
retval := retval || 'g';
ELSIF w BETWEEN 47614 AND 48118 THEN
retval := retval || 'h';
ELSIF w BETWEEN 48119 AND 49061 THEN
retval := retval || 'j';
ELSIF w BETWEEN 49062 AND 49323 THEN
retval := retval || 'k';
ELSIF w BETWEEN 49324 AND 49895 THEN
retval := retval || 'l';
ELSIF w BETWEEN 49896 AND 50370 THEN
retval := retval || 'm';
ELSIF w BETWEEN 50371 AND 50613 THEN
retval := retval || 'n';
ELSIF w BETWEEN 50614 AND 50621 THEN
retval := retval || 'o';
ELSIF w BETWEEN 50622 AND 50905 THEN
retval := retval || 'p';
ELSIF w BETWEEN 50906 AND 51386 THEN
retval := retval || 'q';
ELSIF w BETWEEN 51387 AND 51445 THEN
retval := retval || 'r';
ELSIF w BETWEEN 51446 AND 52216 THEN
retval := retval || 's';
ELSIF w BETWEEN 52218 AND 52697 THEN
retval := retval || 't';
ELSIF w BETWEEN 52698 AND 52979 THEN
retval := retval || 'w';
ELSIF w BETWEEN 52980 AND 53688 THEN
retval := retval || 'x';
ELSIF w BETWEEN 53689 AND 54480 THEN
retval := retval || 'y';
ELSIF w BETWEEN 54481 AND 55289 THEN
retval := retval || 'z';
ELSE
retval := retval || '错误';
END IF;
END IF;
-- 移除第一个字符,更新字符串和长度
s := SUBSTRING(s FROM 2 FOR l - 1);
l := l - 1;
END LOOP;
RETURN retval;
END;
$$;
其他链接
- 获取中文的十六进制编码、GB2312 简体中文编码表、GBK 编码表
- 进制转换
- 国标-中文编码字符集(GB18030-2005)、GB18030-2000
- ASCII、GB2312、GBK、GB18030 编码关系(GB18030>GBK>GB2312>ASCII,虽然GBK 系列字符包含了 ASCII 字符,但是并非严格上的 ASCII 超集。也就导致了pg 数据库服务端编码不支持 GBK 字符集。)
- ASCII 是 7 位编码,表示了2^7 = 0-127,最高位只能是 0
- GBK等是单字节、双字节、四字节编码方式,比如上面的“濮”,双字节编码为0xE5 0xA7。对应二进制是11100101 10100111。最高位却是 1
- 所以虽然兼容,但非严格的超集(ASCII 超集定义是每个字符的最高位为 0)。