繁体   English   中英

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

[英]Hibernate Search with Autocomplete and Fuzzy-Functionality

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

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

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

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

因此,用户应该能够键入“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() 方法相同,只是试图添加模糊支持)

然而,我猜这与minGramSize的 1 和WhitespaceTokenizerFactory相矛盾?

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

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

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

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

然而,我猜这与 1 的 minGramSize 和 WhitespaceTokenizerFactory 相矛盾?

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

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

如果您想以任何顺序匹配包含所有单词的文档,那么......这不太明显。 Hibernate 搜索(尚未)中不支持该功能,因此您基本上必须自己构建查询。 我已经看到的一个 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 ,它更适合忽略非常频繁的术语。 有关设置,请参见此处 这应该会提高使用相同分析仪的评分。

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

@Field(name = "name", analyzer = @Analyzer(definition = "text")
@Field(name = "name_autocomplete", analyzer = @Analyzer(definition = "edgeNgram")
private String name;

分析器“文本”只是您链接的答案中的分析器“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);

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM