PostgreSQL与JS非英文大小写转换不一致场景及优化咨询
背景
我创建了如下SQL表结构,通过检查约束强制仅接受小写单词插入:
CREATE TABLE "word_tokens" ( "token_id" bigint generated always as identity, "word" varchar(400), primary key ("token_id"), check constraint "cc_word_tokens_lower" (LOWER("word") = "word") ) CREATE UNIQUE INDEX "ux_word_tokens_words" ON "word_tokens" ("word");
后端基于Node.js,处理逻辑为:
word = word.toLocaleLowerCase(); pg.query `INSERT INTO "word_tokens"("word") VALUES (${word});`
疑问
非英文场景下,JavaScript的toLocaleLowerCase()与PostgreSQL的LOWER()是否存在转换结果不一致的情况?同时有以下几个具体问题:
- 是否需要使用normalize方法?
- 是否应该用
string.toLowerCase()替代string.toLocaleLowerCase()? - 改用大写转换是否能减少不一致情况?
已验证select lower('Ş') = 'ş'返回true,但想了解可能出现不一致的场景。
解答
一、存在不一致的场景
确实存在部分字符在两者转换后结果不同的情况,典型案例包括:
- 德语ß字符:JavaScript中
'ß'.toLocaleLowerCase('de-DE')返回'ß'(ß本身为小写,德语中其大写形式为SS);而PostgreSQL中,LOWER('ẞ')(大写ß)返回'ß',但如果JS传入'ss',PostgreSQL的检查约束会认可该值,但ss和ß在德语中语义相近却会被当作不同字符串存储,引发重复问题。 - 土耳其语I/i字符:土耳其语中,大写I的小写形式是
ı(无点i),小写i的大写形式是İ(有点I)。若JavaScript使用'I'.toLocaleLowerCase('tr-TR')会返回'ı',但如果PostgreSQL数据库的lc_ctype未设置为tr_TR.UTF-8,LOWER('I')会返回'i',两者结果不一致。 - Unicode组合字符差异:比如字符
DŽ(U+01C4),JavaScript的toLocaleLowerCase可能转换为Dž(U+01C5),而PostgreSQL的LOWER可能处理为dž(U+01C6),这类差异源于两者对Unicode规范的实现版本或细节处理不同。
二、具体问题解答
是否需要使用normalize方法?
需要。同一语义的字符可能存在多种Unicode表示形式(比如预合成字符é(U+00E9)和组合字符e(U+0065)+´(U+0301))。JavaScript和PostgreSQL对这类字符的处理逻辑可能不同,未归一化的字符可能导致转换后看似相同的字符串实际编码不同,触发检查约束失败或重复存储。建议使用word.normalize('NFC')统一字符的标准表示形式。是否应该用
string.toLowerCase()替代string.toLocaleLowerCase()?
不建议。toLowerCase()不考虑区域设置,对于依赖区域规则的字符(如土耳其语的I、德语的ß)转换结果不符合语言习惯,反而会增加不一致概率。如果业务涉及多语言,建议明确指定区域参数调用toLocaleLowerCase()(比如toLocaleLowerCase('tr-TR')),确保转换符合目标语言规则。改用大写转换是否能减少不一致情况?
不会。大小写转换的区域规则差异是对称存在的,比如德语ß转大写时,JS和PostgreSQL在正确区域设置下都会返回SS,但土耳其语i转大写时,JS的'i'.toLocaleUpperCase('tr-TR')返回'İ',若PostgreSQL区域未配置正确则返回'I',同样会出现不一致。转换方向不影响区域规则带来的差异问题。
内容的提问来源于stack exchange,提问作者Akash Kava

