【问题标题】:Hibernate Search with Autocomplete and Fuzzy-Functionality具有自动完成和模糊功能的休眠搜索
【发布时间】:2020-04-16 13:46:13
【问题描述】:

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

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

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

我正在使用出色答案 here 中提供的自定义查询和索引分析器,因为我需要 minGramSize 为 1 以允许自动完成功能,同时我还希望多词用户输入分开通过空格,例如“彼得的欧元帐户”,可以在不同的情况下(下或上)。

所以用户应该能够输入“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 相矛盾?

【问题讨论】:

    标签: java hibernate lucene hibernate-search


    【解决方案1】:

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

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

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

      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 (yet) 不支持此功能,因此您基本上必须自己构建查询。我已经看到的一个 hack 是这样的:

    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;
    

    分析器“文本”只是来自answer you linked 的分析器“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);
    

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

    【讨论】:

    • 感谢您的精彩回答 yrodiere,但不幸的是,即使在提升时,精确匹配也不会获得任何优先权。然而,更严重的问题是查询“from bo”返回名称为“BBS”​​和“EUR Load Testing”的文档。什么可能导致此问题 - 是否与我的原始设置有关?
    • “加载”以“Lo”开头,在“Bo”的1个编辑距离内,所以匹配。 "BBS" => "BB" => 匹配 "Bo"。这对你来说是模糊搜索......关于评分,我更新了我的答案。
    猜你喜欢
    • 1970-01-01
    • 2012-08-05
    • 1970-01-01
    • 2023-01-20
    • 1970-01-01
    • 2021-03-31
    • 2021-07-09
    • 2020-06-08
    • 2019-03-08
    相关资源
    最近更新 更多