Java实现PostgreSQL词表与商标表完整词匹配方案咨询
解决PostgreSQL词汇表与商标表的完整词/词组匹配问题
问题背景
现有两个PostgreSQL表:
CREATE TABLE IF NOT EXISTS words ( id bigint NOT NULL DEFAULT nextval('processed_words_id_seq'::regclass), keyword character varying(300) COLLATE pg_catalog."default", ); INSERT INTO words (keyword) VALUES ('while swam is interesting', 'ibm is a company like bmw'); CREATE TABLE IF NOT EXISTS trademarks ( id bigint NOT NULL DEFAULT nextval('trademarks_id_seq'::regclass), trademark character varying(300) COLLATE pg_catalog."default", ); INSERT INTO trademarks (trademark) VALUES ('while swam', 'ibm', 'bmw');
需求是对比words.keyword字段内容,匹配trademarks.trademark中的完整词或词组,禁止部分匹配(例如aus不能匹配australia)。当前Java代码使用indexOf方法,会触发错误的部分匹配,需要修正。
解决方案
方案一:改进Java代码,正则匹配完整单元
核心是利用正则表达式的边界匹配逻辑,确保匹配的是独立的词/词组,而非字符串片段。同时要处理词组中的特殊字符,避免正则语法冲突。
修改findPhrasesInDocument方法:
List<String> findPhrasesInDocument(String doc, List<String> phrases) { List<String> foundPhrases = new ArrayList<>(); for (String phrase : phrases) { // 转义词组中的正则特殊字符(如.、*等) String escapedPhrase = Pattern.quote(phrase); // 匹配规则:词组前后是空白字符或文档首尾,确保是完整单元 String regex = String.format("(^|\\s)%s($|\\s)", escapedPhrase); // 可选忽略大小写,根据业务需求调整 Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE); Matcher matcher = pattern.matcher(doc); if (matcher.find()) { foundPhrases.add(phrase); } } return foundPhrases; }
如果业务允许单词边界(如while可以匹配while.这类带标点的情况),可以把正则改成\\b%s\\b,但该规则对包含非单词字符(如连字符)的词组兼容性较差。
方案二:数据库层面匹配(适合大量商标场景)
当trademarks表有数千条数据时,把所有商标加载到Java内存循环匹配效率较低,建议直接在SQL中完成匹配,减少数据传输开销。
方式1:正则匹配SQL
编写原生SQL直接关联两张表,匹配完整词/词组:
SELECT t.trademark FROM words w JOIN trademarks t ON w.keyword ~* ('\m' || t.trademark || '\M') WHERE w.id = ?; -- 传入待检查的words.id
~*是PostgreSQL不区分大小写的正则匹配运算符\m表示单词开头,\M表示单词结尾,确保匹配完整单元
方式2:全文索引优化(高频查询场景)
如果查询频率高,给字段创建全文索引可以大幅提升匹配速度:
-- 创建全文索引 CREATE INDEX idx_words_keyword_tsv ON words USING GIN (to_tsvector('english', keyword)); CREATE INDEX idx_trademarks_trademark_tsv ON trademarks USING GIN (to_tsvector('english', trademark)); -- 查询匹配的商标 SELECT t.trademark FROM words w JOIN trademarks t ON to_tsvector('english', w.keyword) @@ to_tsquery('english', replace(t.trademark, ' ', ' & ')) WHERE w.id = ?;
replace(t.trademark, ' ', ' & ') 把多词词组转换成全文查询格式,确保整个词组被完整匹配。
代码整合示例(数据库查询方式)
修改Java业务逻辑,直接通过SQL获取匹配结果:
@Override public void execute(JobExecutionContext context) { Optional<ProcessedWords> keywordOpt = processedWordsService.findRandomKeywordWhereTrademarkBlacklistedIsEmpty(); if (keywordOpt.isPresent()) { ProcessedWords processedWords = keywordOpt.get(); System.out.println("Checking keyword: " + processedWords.getKeyword()); // 调用DAO查询匹配的商标 List<String> matchedTrademarks = processedWordsService.findMatchedTrademarks(processedWords.getId()); if (!matchedTrademarks.isEmpty()) { String joinedTrademarks = String.join(", ", matchedTrademarks); System.out.println("Found blacklisted word in keyword: " + joinedTrademarks); processedWordsService.updateTrademarkBlacklistedById(processedWords.getId(), joinedTrademarks); } } } // 在ProcessedWordsRepository中添加查询方法 @Query(value = "SELECT t.trademark FROM words w JOIN trademarks t ON w.keyword ~* ('\\m' || t.trademark || '\\M') WHERE w.id = :wordId", nativeQuery = true) List<String> findMatchedTrademarks(@Param("wordId") long wordId);
内容的提问来源于stack exchange,提问作者Peter Penzov
相关产品推荐
相关产品推荐

