如何处理 Lucene 中的标识符字段?

How to deal with identifier fields in Lucene?

我偶然发现了一个类似于 中描述的问题:我有一个名为 'type' 的字段,它是一个标识符,即它区分大小写,我想使用它进行精确搜索,没有标记化,没有相似性搜索,只是简单的 "find exactly 'Sport:01'"。我可能会从 'Sport*' 中获益,但对我来说这不是特别重要。

我无法让它工作:我认为存储它的正确字段类型是:StringField.TYPE_STOREDDOCS_AND_FREQS_AND_POSITIONSsetOmitNorms ( true )。但是,这种方式我无法正确解析查询,如: +type:"RockMusic" +title: "a sample title" 使用标准分析器,因为据我所知,分析器将输入转换为小写(即 rockmusic)并存储类型以其原始的 mixed-case 形式(因此,即使我删除了标题条款,我也无法解决它)。

我想混合使用 case-insensitive 搜索标题和 case-sensitive 搜索类型,因为我遇到过 type := BRAIN 是首字母缩写词的情况,它不同于 'Brain'.

那么,管理上述字段和搜索的最佳方式是什么?除了文本和字符串字段,还有其他选择吗?

我使用的是 Lucene 6.6.0,但这是一个普遍问题,涉及多个(全部?)Lucene 版本。

一些显示细节的代码是 here (see testIdMixedCaseID*). The real use case 比较复杂,如果你想看一下,问题出在字段 CC_FIELD 上,可能是 'BioProc' 什么都没有可以找到这样的案例。

请注意我需要使用普通的 Lucene,而不是 Solr 或 Elastic 搜索。

以下注释基于 Lucene 8.x,而非 Lucene 6.6 - 因此可能存在一些语法差异 - 但我同意你的观点,即任何此类差异如何与你的问题巧合。

这里有一些注释,我将重点关注您问题的以下方面:

However, this way I can't correctly resolve a query like: +type:"RockMusic" +title:"a sample title" using the standard analyzer

我认为这有两个部分:

首先,使用 "a sample title" 的查询示例将 - 如您所说 - 不能很好地与标准分析器的工作方式一起工作 - 出于您陈述的原因。

但是,其次,可以将您要使用的两种类型的查询结合起来,以一种我相信可以满足您需要的方式:exact 匹配type 字段(例如 RockMusic)和 title 字段(a sample title)的更传统的标记化和 case-insensitive 结果。

我会这样做:

这里是一些简单的测试数据:

public static void buildIndex() throws IOException {
    final Directory dir = FSDirectory.open(Paths.get(INDEX_PATH));
    Analyzer analyzer = new StandardAnalyzer();
    IndexWriterConfig iwc = new IndexWriterConfig(analyzer);
    iwc.setOpenMode(OpenMode.CREATE);
    Document doc;

    try (IndexWriter writer = new IndexWriter(dir, iwc)) {
        doc = new Document();
        doc.add(new StringField("type", "RockMusic", Field.Store.YES));
        doc.add(new TextField("title", "a sample title", Field.Store.YES));
        writer.addDocument(doc);

        doc = new Document();
        doc.add(new StringField("type", "RockMusic", Field.Store.YES));
        doc.add(new TextField("title", "another different title", Field.Store.YES));
        writer.addDocument(doc);

        doc = new Document();
        doc.add(new StringField("type", "Rock Music", Field.Store.YES));
        doc.add(new TextField("title", "a sample title", Field.Store.YES));
        writer.addDocument(doc);

    }
}

查询代码如下:

public static void doSearch() throws QueryNodeException, ParseException, IOException {

    IndexReader reader = DirectoryReader.open(FSDirectory.open(Paths.get(INDEX_PATH)));
    IndexSearcher searcher = new IndexSearcher(reader);

    TermQuery typeQuery = new TermQuery(new Term("type", "RockMusic"));

    Analyzer analyzer = new StandardAnalyzer();
    QueryParser parser = new QueryParser("title", analyzer);
    Query titleQuery = parser.parse("A Sample Title");

    Query query = new BooleanQuery.Builder()
            .add(typeQuery, BooleanClause.Occur.MUST)
            .add(titleQuery, BooleanClause.Occur.MUST)
            .build();

    System.out.println("Query: " + query.toString());
    System.out.println();

    TopDocs results = searcher.search(query, 100);
    ScoreDoc[] hits = results.scoreDocs;
    for (ScoreDoc hit : hits) {
        System.out.println("doc = " + hit.doc + "; score = " + hit.score);
        Document doc = searcher.doc(hit.doc);
        System.out.println("Type = " + doc.get("type")
                + "; Title = " + doc.get("title"));
        System.out.println();
    }
}

上述查询的输出如下:

Query: +type:RockMusic +(title:a title:sample title:title)

doc = 0; score = 0.7016101
Type = RockMusic; Title = a sample title

doc = 1; score = 0.2743341
Type = RockMusic; Title = another different title

如您所见,此查询与从您的问题中提取的查询略有不同。

但找到的文档列表显示 (a) 根本找不到 Rock Music 文档(好 - 因为 Rock Music 与 "type" 的搜索词不匹配 RockMusic); (b) 在搜索 A Sample Title.

时,标题 a sample title 的匹配分数远高于 another different title 文档

补充说明:

此查询通过将 StringField 精确搜索与更传统的 TextField 标记化搜索相结合来工作 - 后一种搜索由 StandardAnalyzer 处理(匹配数据在中的索引方式第一名)。

我假设分数排名对您有用 - 但对于标题搜索,我认为这是合理的。

对于 StringField 数据,此方法也适用于您的 BRAINbrain 示例。

(我还假设,对于用户界面,用户可以 select 来自 drop-down 的 "RockMusic" 类型值,然后输入 "A Sample Title" 搜索一个输入字段 - 但我认为这是 off-topic。

您显然可以根据需要增强分析器以包含 stop-words 等。

当然,我的示例涉及 hard-coded 数据 - 但推广这种处理 dynamically-provided 搜索词的方法并不需要太多。

希望这是有道理的 - 并且我正确理解了问题。

我自己要回答...

我发现了@andrewjames 在 by making a number of tests of my own 中概述的内容。本质上,像 "type" 这样的字段在标准分析器中表现不佳,最好使用像 KeywordAnalyzer 这样的分析器对其进行索引和搜索,实际上,它按原样存储原始值并相应地进行搜索。

大多数真实案例都像我的例子,即混合类 ID 字段,需要精确匹配,加上像 'title' 或 'description' 这样的字段,最适合使用 per-token 的用户搜索搜索、基于词的评分、停用词消除等

因此,PerFieldAnalyzerWrapper(另请参阅上面链接的我的示例代码)提供了很多帮助,即包装分析器,它能够根据字段名称调度分析字段特定的分析器基础。

要补充的一件事是,我仍然不清楚在没有解析器的情况下构建查询时使用哪个分析器(例如,使用新的TermQuery ( new Term ( fname, fval )),所以现在我使用QueryParser.