对来自 Lucene 索引的结果进行分类

Categorize results coming from a Lucene index

我有一个 Lucene 索引,它是在 Hibernate Search 注释的帮助下通过 Hibernate 生成的,有 3 个字段(只是为了简化一点)描述一篇文章:

id, title, brand

内容示例:

id, title, brand 1, "Long skirt", "Sweet and Gabbana" 2, "Sweet neck vest", "Armani" 3, "Sweet feeling shirt", "Armani"

注意 "Sweet and Gabbana"、"Sweet neck vest" 和 "Sweet feeling shirt" 如何分享 "sweet"。

我想进行 Lucene 查询,这样,如果我搜索关键字 "sweet",我会得到 2 个不同的类别,一个用于标题,另一个用于品牌。例如:

换句话说,我想向用户展示系统在这两个不同类别中找到的结果。

当我 运行 查询(标题和品牌之间的一种 OR)时,我得到了所有三个条目(在 Lucene 中,id 为 1、2 和 3 的文档)只包含一个属性或另一个,但我该如何对它们进行分类?

@PersistenceContext
private EntityManager em;

...

@Override
public List<ArticleByIndexModel> retrieveArticlesSearchQueryResult(final String searchString,
        final String languageIso639) {

    final FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(em);
    final org.apache.lucene.search.Query luceneQuery = buildUpArticlesSearchLuceneQuery(searchString,
            languageIso639, fullTextEntityManager);

    final String titleFieldName = ArticleTranslationFieldPrefixes.TITLE + languageIso639;
    final String brandNameFieldName = BrandTaxonomy.BrandTaxonomyNameFieldName.NAME;

    final FullTextQuery fullTextQuery = fullTextEntityManager.createFullTextQuery(luceneQuery);
    fullTextQuery.setMaxResults(50);
    fullTextQuery.setProjection(Article_.articleID.getName(), titleFieldName, brandNameFieldName,
            Article_.brandSku.getName(), FullTextQuery.DOCUMENT_ID, FullTextQuery.EXPLANATION, FullTextQuery.THIS);

    @SuppressWarnings("unchecked")
    final List<Object[]> list = (List<Object[]>) fullTextQuery.getResultList();

    final List<ArticleByIndexModel> resultList = list.stream()
            .map(x -> new ArticleByIndexModel((Integer) x[0], (String) x[1])).collect(Collectors.toList());
    return resultList;
}

private org.apache.lucene.search.Query buildUpArticlesSearchLuceneQuery(final String searchString,
        final String languageIso639, final FullTextEntityManager fullTextEntityManager) {

    final String brandSkuName = Article_.brandSku.getName();

    final String analyzerPartName = ArticleTranslationDiscriminator.getAnalyzerPartNameByLanguage(languageIso639);
    final String titleFieldName = ArticleTranslationFieldPrefixes.TITLE + languageIso639;
    final String titleEdgeNGramFieldName = ArticleTranslationFieldPrefixes.TITLE_EDGE_N_GRAM + languageIso639;
    final String titleNGramFieldName = ArticleTranslationFieldPrefixes.TITLE_N_GRAM + languageIso639;

    final String brandNameEdgeNGramFieldName = BrandTaxonomy.BrandTaxonomyNameFieldName.NAME_EDGE_N_GRAM;
    final String brandNameNGramFieldName = BrandTaxonomy.BrandTaxonomyNameFieldName.NAME_N_GRAM;

    final SearchFactory searchFactory = fullTextEntityManager.getSearchFactory();
    final QueryBuilder qb = searchFactory.buildQueryBuilder().forEntity(Article.class)
            .overridesForField(titleFieldName, ArticleTranslationFieldPrefixes.TITLE + analyzerPartName)
            .overridesForField(titleEdgeNGramFieldName,
                    ArticleTranslationFieldPrefixes.TITLE_EDGE_N_GRAM + analyzerPartName)
            .overridesForField(titleNGramFieldName, ArticleTranslationFieldPrefixes.TITLE_N_GRAM + analyzerPartName)
            .get();

    final org.apache.lucene.search.Query luceneQuery =
            /**/
            qb.bool()
                    /**/
                    .should(qb.phrase().withSlop(2).onField(titleNGramFieldName).andField(titleEdgeNGramFieldName)
                            .boostedTo(5).sentence(searchString.toLowerCase()).createQuery())
                    /**/
                    .should(qb.phrase().withSlop(2).onField(brandNameNGramFieldName)
                            .andField(brandNameEdgeNGramFieldName).boostedTo(5).sentence(searchString.toLowerCase())
                            .createQuery())
                    /**/
                    .should(qb.keyword().onField(brandSkuName).matching(searchString.toLowerCase()).createQuery())
                    /**/
                    .createQuery();

    return luceneQuery;
}

我看不到进行 2 个不同查询然后合并结果的解决方案。

我了解了 Facets,但我认为它们不适合这种情况。

你有什么想法吗?

谢谢!!!

我假设您需要将结果作为单个列表显示给用户,每个列表都有一些描述(因为 title/matched 因为 brand/both 而匹配)。

我认为在 Hibernate Search 中没有一项功能可以让您完全做到这一点。我想有一些方法可以使用 low-level Lucene API(收集器)来完成,但这会涉及一些黑魔法,我认为我们无法将其插入 Hibernate Search。

所以让我们走更简单的路:自己动手。

就我个人而言,我只会 运行 多个查询:

  • 第一次就像您在示例中所做的那样
  • 第二次投影到 ID (.setProjection(ProjectionContants.ID)) 并仅使用两个子句:一个要求匹配具有与第一个查询结果之一相同的 ID(基本上 must(should(id=<firstID>), should(id=<secondID>), ... ),和一个要求搜索字符串与标题匹配(基本上 must(title=<searchString>)
  • 第三次与第二次类似,但使用的是品牌而不是标题

然后我会使用第二个和第三个查询的结果来确定给定的结果是因为标题还是因为品牌而匹配。

当然,只有当搜索字符串仅预期完全匹配标题或品牌(或两者)时才有效,如果搜索字符串的某些部分与标题匹配,而其他部分与品牌。但如果这就是你想要的,那么你当前的查询无论如何都是错误的...