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



我偶然发现了一个类似于另一个问题中描述的问题:我有一个名为"type"的字段,这是一个标识符,即它区分大小写,我想使用它进行精确搜索,没有标记化,没有相似性搜索,只是简单的"准确找到'Sport:01'"。我可能会从"运动*"中受益,但就我而言,这并不重要。

我无法使其工作:我认为存储此字段的正确类型是:StringField.TYPE_STORED,带有DOCS_AND_FREQS_AND_POSITIONSsetOmitNorms ( true )。但是,这样我就无法正确解析以下查询:+type:"RockMusic" +title: "a sample title"使用标准分析器,因为据我了解,分析器将输入转换为小写(即摇滚音乐),并且类型以其原始混合大小写形式存储(因此,即使我删除标题子句,我也无法解析它)。

我想将标题上的不区分大小写的搜索与区分大小写的类型混合在一起,因为我的情况是 type :=BRAIN是一个首字母缩略词,它与"大脑"不同。

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

我使用的是Lucene 6.6.0,但这是一个关于多个(全部?卢塞恩版本。

此处显示详细信息的一些代码(请参阅testIdMixedCaseID*)。真正的用例相当复杂,如果你想看一下,问题出在现场CC_FIELD,这可能是"BioProc",在这种情况下什么也找不到。

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

以下注释基于 Lucene 8.x,而不是 Lucene 6.6 - 因此可能存在一些语法差异 - 但我接受你的观点,即任何此类差异都应该是你的问题的巧合。

以下是一些注释,我将重点介绍您问题的以下方面:

但是,这样我就无法使用标准分析器正确解析以下查询: +类型:"摇滚音乐" +标题:"示例标题">

我认为这有两个部分:

首先,正如您所说,使用"a sample title"的查询示例将不适用于标准分析器的工作方式 - 原因您说明。

但是,其次,可以组合您要使用的两种类型的查询,我相信可以满足您的需求:type字段的完全匹配(例如RockMusic)和更传统的标记化和不区分大小写的结果用于title字段(a sample title)。

以下是我将如何做到这一点:

以下是一些简单的测试数据:

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 MusicRockMusic的"类型"搜索词不匹配);(b)在搜索A Sample Title时,标题a sample titleanother different title文档获得的匹配分数高得多。

附加说明:

此查询的工作原理是将StringField精确搜索与更传统的TextField标记化搜索相结合 - 后一种搜索由StandardAnalyzer处理(与数据最初索引的方式匹配)。

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

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

(我还假设,对于用户界面,用户可以从下拉列表中选择"RockMusic"类型值,然后在输入字段中输入"示例标题"搜索 - 但我认为这偏离了主题)。

您可以根据需要明显增强分析器以包含停用词等。

当然,我的示例涉及硬编码数据 - 但是推广这种方法来处理动态提供的搜索词并不需要太多。

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

要回答我自己...

我通过自己做一些测试,发现了@andrewjames在他的出色分析中概述的内容。从本质上讲,像"type"这样的字段不能很好地与标准分析器配合使用,最好使用像KeywordAnalyzer这样的分析器进行索引和搜索,实际上,它按原样存储原始值并相应地进行搜索。

大多数真实案例就像我的例子一样,即混合的类似ID的字段,需要精确匹配,加上"标题"或"描述"等字段,它最好地服务于使用每个令牌搜索的用户搜索,基于单词的评分,停用词消除等。

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

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

最新更新