具有自动完成和模糊功能的 Hibernate 搜索
Hibernate Search with Autocomplete and Fuzzy-Functionality
我正在尝试创建 StingUtils containsIgnoreCase() 方法的 Hibernate 搜索表示以及模糊搜索匹配。
假设用户写下字母"p",他们将得到包含字母"p"的所有匹配项(无论该字母位于相应匹配项的开头、中间还是结尾).
当它们形成诸如 "Peter" 之类的词时,它们也应该接收模糊匹配,例如 "Petar"、"Petaer" 和 "Peder"。
我正在使用很好的答案 中提供的自定义查询和索引分析器,因为我需要 minGramSize
在 1 以允许自动完成功能,同时我也希望由空格分隔的多词用户输入,例如 "EUR Account of Peter",可以是不同的大小写(小写或大写)。
因此用户应该能够键入 "AND" 并接收上面的示例作为匹配项。
目前,我正在使用以下查询:
org.apache.lucene.search.Query fuzzySearchByName = qb.keyword().fuzzy()
.withEditDistanceUpTo(1).onField("name")
.matching(userInput).createQuery();
booleanQuery.add(fuzzySearchByName, BooleanClause.Occur.MUST);
但是,完全匹配案例不会出现在搜索结果中:
如果我们输入 "petar",我们会得到以下结果:
- Petarr(非精确匹配)
- Petaer(非精确匹配)
... 4. PETAR(完全匹配)
同样适用于用户输入 "peter",其中第一个结果是 "Petero",第二个是 "Peter"(第二个应该是第一个)。
我还需要在多词查询中只包含完全匹配 - 例如如果我开始写“Account for...”,我希望所有匹配的结果都包含短语“Account for”,并最终将其模糊化- 基于该短语的相关术语(与前面显示的 containsIgnoreCase() 方法基本相同,只是试图添加模糊支持)。
不过我想这与 1 的 minGramSize
和 WhitespaceTokenizerFactory
?
相矛盾
However, exact match cases do not receive presendence in the search results:
只需使用两个个查询而不是一个:
编辑:您还需要为自动完成和"exact" 匹配设置两个单独的字段;在底部查看我的编辑。
org.apache.lucene.search.Query exactSearchByName = qb.keyword().onField("name")
.matching(userInput).createQuery();
org.apache.lucene.search.Query fuzzySearchByName = qb.keyword().fuzzy()
.withEditDistanceUpTo(1).onField("name")
.matching(userInput).createQuery();
org.apache.lucene.search.Query searchByName = qb.boolean().should(exactSearchByName).should(fuzzySearchByName).createQuery();
booleanQuery.add(searchByName, BooleanClause.Occur.MUST);
这将匹配完全 或 包含用户输入的文档,因此这将匹配与您的示例相同的文档。但是,包含用户输入的文档将完全匹配这两个查询,而仅包含类似内容的文档将只匹配模糊查询。因此,完全匹配的分数会更高,最终会在结果列表中排名靠前。
如果精确匹配度不够高,请尝试向 exactSearchByName
查询添加提升:
org.apache.lucene.search.Query exactSearchByName = qb.keyword().onField("name")
.matching(userInput)
.boostedTo(4.0f)
.createQuery();
I guess however that this contradics with the minGramSize of 1 and the WhitespaceTokenizerFactory?
如果您想匹配包含出现在用户输入中的任何单词(但不一定是所有单词)的文档,并将包含更多单词的文档放在结果列表中靠前的位置,请按照我上面的说明进行操作。
如果要匹配包含所有单词的顺序完全相同的文档,请使用 KeywordTokenizerFactory
(即不标记化)。
如果您想要匹配以任意顺序包含所有单词的文档,嗯……这不太明显。 Hibernate Search (yet) 对此不提供支持,因此您基本上必须自己构建查询。我已经见过的一种技巧是这样的:
Analyzer analyzer = fullTextSession.getSearchFactory().getAnalyzer( "myAnalyzer" );
QueryParser queryParser = new QueryParser( "name", analyzer );
queryParser.setOperator( Operator.AND ); // Match *all* terms
Query luceneQuery = queryParser.parse( userInput );
...但这不会生成模糊查询。如果你想要模糊查询,你可以尝试重写QueryParser的自定义子类中的一些方法。我没试过,但它可能有效:
public final class FuzzyQueryParser extends QueryParser {
private final int maxEditDistance;
private final int prefixLength;
public FuzzyQueryBuilder(String fieldName, Analyzer analyzer, int maxEditDistance, int prefixLength) {
super( fieldName, analyzer );
this.maxEditDistance = maxEditDistance;
this.prefixLength = prefixLength;
}
@Override
protected Query newTermQuery(Term term) {
return new FuzzyQuery( term, maxEditDistance, prefixLength );
}
}
编辑:当 minGramSize 为 1 时,您会得到很多非常频繁的术语:从单词开头提取的单个或两个字符的术语。它可能会导致许多不需要的匹配项会被高分(因为这些术语很频繁)并且可能会淹没精确匹配项。
首先,您可以尝试将相似度(~评分公式)设置为org.apache.lucene.search.similarities.BM25Similarity
,这样可以更好地忽略非常频繁的术语。参见 here for the setting。这应该会提高使用相同分析器的评分。
其次,您可以尝试设置 两个 字段而不是一个:一个字段用于模糊自动完成,一个用于非模糊、完全匹配。这可能会提高精确匹配的分数,因为为用于精确匹配的字段索引的无意义术语会更少。只需这样做:
@Field(name = "name", analyzer = @Analyzer(definition = "text")
@Field(name = "name_autocomplete", analyzer = @Analyzer(definition = "edgeNgram")
private String name;
分析器 "text" 就是来自 的分析器 "edgeNGram_query";只需重命名即可。
继续编写两个查询而不是如上所述的一个,但请确保针对两个不同的字段:
org.apache.lucene.search.Query exactSearchByName = qb.keyword().onField("name")
.matching(userInput).createQuery();
org.apache.lucene.search.Query fuzzySearchByName = qb.keyword().fuzzy()
.withEditDistanceUpTo(1).onField("name_autocomplete")
.matching(userInput).createQuery();
org.apache.lucene.search.Query searchByName = qb.boolean().should(exactSearchByName).should(fuzzySearchByName).createQuery();
booleanQuery.add(searchByName, BooleanClause.Occur.MUST);
当然,不要忘记在这些更改后重新编制索引。
我正在尝试创建 StingUtils containsIgnoreCase() 方法的 Hibernate 搜索表示以及模糊搜索匹配。
假设用户写下字母"p",他们将得到包含字母"p"的所有匹配项(无论该字母位于相应匹配项的开头、中间还是结尾).
当它们形成诸如 "Peter" 之类的词时,它们也应该接收模糊匹配,例如 "Petar"、"Petaer" 和 "Peder"。
我正在使用很好的答案 minGramSize
在 1 以允许自动完成功能,同时我也希望由空格分隔的多词用户输入,例如 "EUR Account of Peter",可以是不同的大小写(小写或大写)。
因此用户应该能够键入 "AND" 并接收上面的示例作为匹配项。
目前,我正在使用以下查询:
org.apache.lucene.search.Query fuzzySearchByName = qb.keyword().fuzzy()
.withEditDistanceUpTo(1).onField("name")
.matching(userInput).createQuery();
booleanQuery.add(fuzzySearchByName, BooleanClause.Occur.MUST);
但是,完全匹配案例不会出现在搜索结果中:
如果我们输入 "petar",我们会得到以下结果:
- Petarr(非精确匹配)
- Petaer(非精确匹配)
... 4. PETAR(完全匹配)
同样适用于用户输入 "peter",其中第一个结果是 "Petero",第二个是 "Peter"(第二个应该是第一个)。
我还需要在多词查询中只包含完全匹配 - 例如如果我开始写“Account for...”,我希望所有匹配的结果都包含短语“Account for”,并最终将其模糊化- 基于该短语的相关术语(与前面显示的 containsIgnoreCase() 方法基本相同,只是试图添加模糊支持)。
不过我想这与 1 的 minGramSize
和 WhitespaceTokenizerFactory
?
However, exact match cases do not receive presendence in the search results:
只需使用两个个查询而不是一个:
编辑:您还需要为自动完成和"exact" 匹配设置两个单独的字段;在底部查看我的编辑。
org.apache.lucene.search.Query exactSearchByName = qb.keyword().onField("name")
.matching(userInput).createQuery();
org.apache.lucene.search.Query fuzzySearchByName = qb.keyword().fuzzy()
.withEditDistanceUpTo(1).onField("name")
.matching(userInput).createQuery();
org.apache.lucene.search.Query searchByName = qb.boolean().should(exactSearchByName).should(fuzzySearchByName).createQuery();
booleanQuery.add(searchByName, BooleanClause.Occur.MUST);
这将匹配完全 或 包含用户输入的文档,因此这将匹配与您的示例相同的文档。但是,包含用户输入的文档将完全匹配这两个查询,而仅包含类似内容的文档将只匹配模糊查询。因此,完全匹配的分数会更高,最终会在结果列表中排名靠前。
如果精确匹配度不够高,请尝试向 exactSearchByName
查询添加提升:
org.apache.lucene.search.Query exactSearchByName = qb.keyword().onField("name")
.matching(userInput)
.boostedTo(4.0f)
.createQuery();
I guess however that this contradics with the minGramSize of 1 and the WhitespaceTokenizerFactory?
如果您想匹配包含出现在用户输入中的任何单词(但不一定是所有单词)的文档,并将包含更多单词的文档放在结果列表中靠前的位置,请按照我上面的说明进行操作。
如果要匹配包含所有单词的顺序完全相同的文档,请使用 KeywordTokenizerFactory
(即不标记化)。
如果您想要匹配以任意顺序包含所有单词的文档,嗯……这不太明显。 Hibernate Search (yet) 对此不提供支持,因此您基本上必须自己构建查询。我已经见过的一种技巧是这样的:
Analyzer analyzer = fullTextSession.getSearchFactory().getAnalyzer( "myAnalyzer" );
QueryParser queryParser = new QueryParser( "name", analyzer );
queryParser.setOperator( Operator.AND ); // Match *all* terms
Query luceneQuery = queryParser.parse( userInput );
...但这不会生成模糊查询。如果你想要模糊查询,你可以尝试重写QueryParser的自定义子类中的一些方法。我没试过,但它可能有效:
public final class FuzzyQueryParser extends QueryParser {
private final int maxEditDistance;
private final int prefixLength;
public FuzzyQueryBuilder(String fieldName, Analyzer analyzer, int maxEditDistance, int prefixLength) {
super( fieldName, analyzer );
this.maxEditDistance = maxEditDistance;
this.prefixLength = prefixLength;
}
@Override
protected Query newTermQuery(Term term) {
return new FuzzyQuery( term, maxEditDistance, prefixLength );
}
}
编辑:当 minGramSize 为 1 时,您会得到很多非常频繁的术语:从单词开头提取的单个或两个字符的术语。它可能会导致许多不需要的匹配项会被高分(因为这些术语很频繁)并且可能会淹没精确匹配项。
首先,您可以尝试将相似度(~评分公式)设置为org.apache.lucene.search.similarities.BM25Similarity
,这样可以更好地忽略非常频繁的术语。参见 here for the setting。这应该会提高使用相同分析器的评分。
其次,您可以尝试设置 两个 字段而不是一个:一个字段用于模糊自动完成,一个用于非模糊、完全匹配。这可能会提高精确匹配的分数,因为为用于精确匹配的字段索引的无意义术语会更少。只需这样做:
@Field(name = "name", analyzer = @Analyzer(definition = "text")
@Field(name = "name_autocomplete", analyzer = @Analyzer(definition = "edgeNgram")
private String name;
分析器 "text" 就是来自
继续编写两个查询而不是如上所述的一个,但请确保针对两个不同的字段:
org.apache.lucene.search.Query exactSearchByName = qb.keyword().onField("name")
.matching(userInput).createQuery();
org.apache.lucene.search.Query fuzzySearchByName = qb.keyword().fuzzy()
.withEditDistanceUpTo(1).onField("name_autocomplete")
.matching(userInput).createQuery();
org.apache.lucene.search.Query searchByName = qb.boolean().should(exactSearchByName).should(fuzzySearchByName).createQuery();
booleanQuery.add(searchByName, BooleanClause.Occur.MUST);
当然,不要忘记在这些更改后重新编制索引。