具有自动完成和模糊功能的 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",我们会得到以下结果:

  1. Petarr(非精确匹配)
  2. Petaer(非精确匹配)

... 4. PETAR完全匹配

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

我还需要在多词查询中只包含完全匹配 - 例如如果我开始写“Account for...”,我希望所有匹配的结果都包含短语“Account for”,并最终将其模糊化- 基于该短语的相关术语(与前面显示的 containsIgnoreCase() 方法基本相同,只是试图添加模糊支持)

不过我想这与 1 的 minGramSizeWhitespaceTokenizerFactory?

相矛盾

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

当然,不要忘记在这些更改后重新编制索引。