无需自定义文本搜索解析器,能否用纯文本搜索替代正则表达式处理数字与单位的格式匹配问题?
首先得给你明确说:不用写C代码的自定义解析器是可以的,但完全抛弃正则预处理可能不太现实——不过我们可以把现有方案优化得更简洁,或者用更贴合PostgreSQL文本搜索生态的方式来解决你这个数字+单位(比如16GB/16 GB)、连字符(HJ-20/HJ 20)的匹配问题。
先聊聊你的现有方案:思路其实很对,就是把数字和单位之间的空格(不管有没有)统一拆成空格,连字符也换成空格,让to_tsvector能把它们识别成独立的词,这样就能正常匹配。但确实嵌套两层regexp_replace再加子查询,代码看起来有点啰嗦,而且以后如果要加新的格式规则(比如处理MB-PS这种单位连字符),正则会越来越臃肿。
先优化你的现有正则方案
其实可以把两次regexp_replace合并成一次,用正则的分支匹配一次性处理两种场景,代码会清爽很多:
select source , query , to_tsvector('portugues_sem_acento', source) @@ plainto_tsquery('portugues_sem_acento', query) as match from ( select regexp_replace(source, '(\d+)\s*(\D+)|(\D+)-(\d+)', '\1\3 \2\4', 'g') as source, regexp_replace(query, '(\d+)\s*(\D+)|(\D+)-(\d+)', '\1\3 \2\4', 'g') as query from (values ('Memória 16 GB parte 32MB HJ-20', '16 gb') , ('Memória 16GB parte 32 MB', '16gb') , ('Memória 16 GB parte 32MB mbx-12', '16gb mbx') , ('Memória 16GB parte 32 MB', '16 gb') ) ts (source, query) ) s
这个正则用|分支同时匹配「数字+可选空格+非数字」和「非数字+连字符+数字」两种情况,一次性替换成带空格的格式,效果和你原来的代码完全一样,但简洁多了。
不用嵌套子查询:把预处理逻辑封装成函数
如果以后要调整格式规则,每次改正则还要找所有用到的地方太麻烦,不如把预处理逻辑封装成一个SQL函数,这样代码更易维护:
-- 先创建你的自定义文本搜索配置(和你原来的一致) create extension if not exists unaccent; drop text search configuration if exists portugues_sem_acento cascade; create text search configuration portugues_sem_acento (copy = portuguese); alter text search configuration portugues_sem_acento alter mapping for asciiword, word, numword, asciihword, hword, numhword, hword_asciipart, hword_part, hword_numpart with unaccent, portuguese_stem; alter database sample_db set default_text_search_config to 'portugues_sem_acento'; set default_text_search_config to 'portugues_sem_acento'; -- 封装预处理函数 create or replace function normalize_search_text(text) returns text as $$ select regexp_replace( $1, '(\d+)\s*(\D+)|(\D+)-(\d+)', '\1\3 \2\4', 'g' -- 全局替换所有匹配项 ); $$ language sql immutable; -- immutable保证函数结果稳定,能被索引缓存 -- 现在查询就非常简洁了 select normalize_search_text(source) as source, normalize_search_text(query) as query, to_tsvector(normalize_search_text(source)) @@ plainto_tsquery(normalize_search_text(query)) as match from (values ('Memória 16 GB parte 32MB HJ-20', '16 gb') , ('Memória 16GB parte 32 MB', '16gb') , ('Memória 16 GB parte 32MB mbx-12', '16gb mbx') , ('Memória 16GB parte 32 MB', '16 gb') ) ts (source, query);
这样以后要加新的格式处理(比如把MB-PS换成MB PS),只需要修改normalize_search_text函数里的正则就行,不用动查询逻辑。
有没有完全不用正则的纯文本搜索方式?
其实PostgreSQL默认的文本搜索解析器会把16GB这种数字+字母的混合词当成一个完整的词,而16 GB会拆成16和GB两个词,所以默认的to_tsvector是没法直接匹配这两种格式的——除非你用自定义解析器,但那需要写C代码,你明确不想这么做。
那如果不想用正则,还有一个替代方案:用pg_trgm扩展的模糊匹配。pg_trgm不关心词的边界,它通过比较字符串的 trigram(三元组)相似性来判断是否匹配,比如16gb和16 gb的相似性非常高,能被匹配到:
-- 先安装pg_trgm扩展 create extension if not exists pg_trgm; -- 调整相似性阈值(默认0.3,你可以根据需求改) set pg_trgm.similarity_threshold = 0.5; select source, query, -- 同时用全文搜索和模糊匹配,兼顾准确性和灵活性 (to_tsvector(source) @@ plainto_tsquery(query)) or (source % query) as match from (values ('Memória 16 GB parte 32MB HJ-20', '16 gb') , ('Memória 16GB parte 32 MB', '16gb') , ('Memória 16 GB parte 32MB mbx-12', '16gb mbx') , ('Memória 16GB parte 32 MB', '16 gb') ) ts (source, query);
不过要注意,pg_trgm的模糊匹配可能会出现一些误匹配(比如相似但不相关的字符串),而且大数据量下性能不如全文搜索,所以最好是结合两者:用全文搜索处理大部分精确匹配的场景,用pg_trgm补漏那些格式不一致的情况。
总结最优方案
- 优先选择:预处理+全文搜索(封装函数):这是最务实的方案,既保持了全文搜索的高性能和准确性,又通过封装函数避免了正则代码的冗余,后期维护也方便。
- 替代方案:pg_trgm模糊匹配:如果不想用正则,可以尝试这种方式,但要注意调整相似性阈值,避免误匹配,同时考虑大数据量下的性能问题。
- 不推荐:完全不用预处理的纯文本搜索:默认解析器不支持拆分数字+字母的混合词,不用预处理的话无法准确匹配
16GB和16 GB这种格式。
备注:内容来源于stack exchange,提问作者Clodoaldo Pinto

