具有自动完成和模糊功能的Hibernate搜索



我正在尝试创建StingUtils containsIgnoreCase()方法的Hibernate Search表示,以及模糊搜索匹配

假设用户写的是字母"p",他们将获得包括字母"p"的所有匹配项(无论字母是位于各个匹配项的开头、中间还是结尾)。

当它们形成诸如"Peter"之类的单词时,它们也应该接受模糊匹配,例如"Petar"、"Petaer"one_answers"Peder"。

我使用的是这里的好答案中提供的自定义查询和索引分析器,因为我需要1处的minGramSize来实现自动完成功能,同时我也希望用空格分隔的多词用户输入,如"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",我们会得到以下结果:

  1. Petarr(不完全匹配)
  2. Petaer(不完全匹配)

。。。4.PETAR(精确匹配)

这同样适用于"peter"的用户输入,其中第一个结果是"Petero",第二个结果为"peter"(第二个应该是第一个)。

我还需要在多词查询中只包括精确匹配-例如,如果我开始写">Account for…",我希望所有匹配的结果都包括短语">Account for",并最终包括基于该短语的模糊相关术语(基本上与前面显示的containsIgnoreCase()方法相同,只是试图添加模糊支持)。

然而,我想这与1的minGramSizeWhitespaceTokenizerFactory相反?

但是,完全匹配的情况不会出现在搜索结果中:

只需使用两个查询,而不是一个:

EDIT:您还需要为自动完成和"精确"匹配设置两个单独的字段;请看底部我的编辑。

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();

然而,我想这与minGramSize为1和WhitespaceTokenizerFactory相矛盾?

如果您想匹配包含用户输入中出现的任何单词(但不一定是所有单词)的文档,并将包含更多单词的文档放在结果列表的更高位置,请执行我上面解释的操作。

如果要匹配包含完全相同顺序的所有单词的文档,请使用KeywordTokenizerFactory(即不使用标记)。

如果你想匹配包含任何顺序的所有单词的文档,那么。。。这就不那么明显了。Hibernate Search(目前)还不支持这一点,所以您基本上必须自己构建查询。我已经看到的一个黑客是这样的:

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 );
}
}

EDIT:如果minGramSize为1,您将获得许多非常频繁的术语:从单词开头提取的单个或两个字符的术语。这可能会导致许多不想要的比赛得分很高(因为这些术语很频繁),并可能淹没准确的比赛。

首先,您可以尝试将相似度(~评分公式)设置为org.apache.lucene.search.similarities.BM25Similarity,它更善于忽略非常频繁的项。有关设置,请参见此处。这应该会提高相同分析器的评分。

其次,您可以尝试设置两个字段,而不是一个:一个字段用于模糊自动完成,另一个用于非模糊完全匹配。这可以提高精确匹配的分数,因为用于精确匹配的字段索引的无意义术语将减少。只需这样做:

@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);

当然,不要忘记在这些变化之后重新索引。

最新更新