如何处理 Lucene 中的标识符字段?
How to deal with identifier fields in Lucene?
我偶然发现了一个类似于 中描述的问题:我有一个名为 'type' 的字段,它是一个标识符,即它区分大小写,我想使用它进行精确搜索,没有标记化,没有相似性搜索,只是简单的 "find exactly 'Sport:01'"。我可能会从 'Sport*' 中获益,但对我来说这不是特别重要。
我无法让它工作:我认为存储它的正确字段类型是:StringField.TYPE_STORED
,DOCS_AND_FREQS_AND_POSITIONS
和 setOmitNorms ( 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
数据,此方法也适用于您的 BRAIN
与 brain
示例。
(我还假设,对于用户界面,用户可以 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
.
我偶然发现了一个类似于
我无法让它工作:我认为存储它的正确字段类型是:StringField.TYPE_STORED
,DOCS_AND_FREQS_AND_POSITIONS
和 setOmitNorms ( 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
数据,此方法也适用于您的 BRAIN
与 brain
示例。
(我还假设,对于用户界面,用户可以 select 来自 drop-down 的 "RockMusic" 类型值,然后输入 "A Sample Title" 搜索一个输入字段 - 但我认为这是 off-topic。
您显然可以根据需要增强分析器以包含 stop-words 等。
当然,我的示例涉及 hard-coded 数据 - 但推广这种处理 dynamically-provided 搜索词的方法并不需要太多。
希望这是有道理的 - 并且我正确理解了问题。
我自己要回答...
我发现了@andrewjames 在
大多数真实案例都像我的例子,即混合类 ID 字段,需要精确匹配,加上像 'title' 或 'description' 这样的字段,最适合使用 per-token 的用户搜索搜索、基于词的评分、停用词消除等
因此,PerFieldAnalyzerWrapper(另请参阅上面链接的我的示例代码)提供了很多帮助,即包装分析器,它能够根据字段名称调度分析字段特定的分析器基础。
要补充的一件事是,我仍然不清楚在没有解析器的情况下构建查询时使用哪个分析器(例如,使用新的TermQuery ( new Term ( fname, fval )
),所以现在我使用QueryParser
.