Lucene - 按一个字段搜索另一个字段排序,回落到辅助字段
Lucene - Search by a field sorting by another, falling back to a secondary field
我希望使用以下字段开发一个简单的搜索
- 标题
- 总结
- 人气
如果有人通过说 "ga" 进行搜索,我将搜索部分匹配的标题(例如 "The Game"),然后按受欢迎程度对这些结果进行排序。
如果有 < 10 个结果,我想返回到摘要。但是,我希望摘要匹配低于任何标题匹配,再次按受欢迎程度排序
例如搜索 "ga*"
"The Game" | "About stuff" | popularity = 3 | (title match)
"Gant charts" | "great stats" | popularity = 7 | (title match)
"Some Title" | "mind the gap" | popularity = 1 | (summary match)
"Another Title" | "blah games" | popularity = 5 | (summary match)
我编写了一个简单的实现,它执行 1 个 Lucene 搜索,如果结果少于 10 个,则对概要进行第二次搜索 - 然后我在语法上合并结果。然而,这并不理想,因为我需要解决重复项,并且分页效果不佳 - 如果可能,最好在 1 中全部完成。
这可能吗?如果可以,怎么做?
(我目前正在使用 Java Lucene jar 进行开发)
这是我目前的尝试(用 Scala 编写)
// Creating the indexes
private def addDoc(w:IndexWriter , clientContent: ClientContent, contentType:String):Unit ={
val doc:Document = new Document()
doc.add(new TextField("title", clientContent.title, Field.Store.YES))
doc.add(new TextField("synopsis", clientContent.synopsis, Field.Store.YES))
doc.add(new StringField("id", clientContent.id, Field.Store.YES))
doc.add(new IntField("popularity", 100000 - clientContent.popularity.day, Field.Store.YES))
doc.add(new StringField("contentType", contentType, Field.Store.YES))
w.addDocument(doc);
}
def createIndex: Unit = {
index = new RAMDirectory()
val analyzer = new StandardAnalyzer(Version.LUCENE_43)
val config = new IndexWriterConfig(Version.LUCENE_43, analyzer)
val w = new IndexWriter(index, config)
clientApplication.shows.list.map {addDoc(w, _, "Show")}
w.close()
reader = IndexReader.open(index)
searcher = new IndexSearcher(reader)
}
// Searching by one field
def dataSearch(queryString: String, section:String, limit:Int):Array[ScoreDoc] = {
val collector = TopFieldCollector.create(
new Sort(new SortField("popularity", SortField.Type.INT, true)),
limit,false,true, true, false);
val analyzer = new StandardAnalyzer(Version.LUCENE_43)
val q = new QueryParser(Version.LUCENE_43, section, analyzer).parse(queryString+"*")
searcher.search(q, collector)
collector.topDocs().scoreDocs
}
// Searching for a query
def search(queryString:String) = {
println(s"search $queryString")
val titleResults = dataSearch(queryString, "title", limit)
if (titleResults.length < limit) {
val synopsisResults = dataSearch(queryString, "synopsis", limit - titleResults.length)
createModel(titleResults ++ synopsisResults)
}
else
createModel(titleResults)
}
可以先按分数排序,再按热度排序,大大提升title的查询。只要与标题匹配的所有字段的分数都相等,并且仅与摘要匹配的文档的分数相等,这样做就可以了:
Sort mySort = new Sort(SortField.FIELD_SCORE, new SortField("popularity", SortField.Type.INT, true));
当然,它们可能不相等。只要提升足够大,idf 就不会成为问题,但是......如果不同文档的字段长度不同,则 lengthNorm 会使分数不相等,除非你禁用了规范。 coord 因素会导致问题,因为匹配 both 字段的文档将比仅匹配标题的文档具有更高的分数。而如果一个匹配词在一个字段中出现不止一次,那么 tf 就会明显不同。
因此,您需要一种方法来简化评分,并防止所有花哨的 lucene 相关性评分逻辑获得成功。您可以使用 ConstantScoreQuery
and DisjunctionMaxQuery
.
获得分数来做您想做的事情
Query titleQuery = new ConstantScoreQuery(new PrefixQuery(new Term("title", queryString)));
titleQuery.setBoost(2);
Query summaryQuery = new ConstantScoreQuery(new PrefixQuery(new Term("title", queryString)));
//Combine with a dismax, so matching both fields won't get a higher score than just the title
Query finalQuery = new DisjnctionMaxQuery(0);
finalQuery.add(titleQuery);
finalQuery.add(summaryQuery);
Sort mySort = new Sort(
SortField.FIELD_SCORE,
new SortField("popularity", SortField.Type.INT, true)
);
val collector = TopFieldCollector.create(mySort,limit,false,true,true,false);
searcher.search(finalQuery, collector);
对于您提供的代码,这将起作用,因为除了构造前缀查询外,您实际上并不需要查询解析器。不过,您也可以保留解析器。 ConstantScoreQuery
是包装器查询。您可以简单地包装从 QueryParser.parse
返回的查询:
QueryParser parser = new QueryParser(Version.LUCENE_43, "title", analyzer);
Query titleQuery = new ConstantScoreQuery(parser.parse(queryString + "*"));
titleQuery.SetBoost(2);
Query summaryQuery = new ConstantScoreQuery(parser.parse("summary:" + queryString + "*"));
我希望使用以下字段开发一个简单的搜索
- 标题
- 总结
- 人气
如果有人通过说 "ga" 进行搜索,我将搜索部分匹配的标题(例如 "The Game"),然后按受欢迎程度对这些结果进行排序。
如果有 < 10 个结果,我想返回到摘要。但是,我希望摘要匹配低于任何标题匹配,再次按受欢迎程度排序
例如搜索 "ga*"
"The Game" | "About stuff" | popularity = 3 | (title match)
"Gant charts" | "great stats" | popularity = 7 | (title match)
"Some Title" | "mind the gap" | popularity = 1 | (summary match)
"Another Title" | "blah games" | popularity = 5 | (summary match)
我编写了一个简单的实现,它执行 1 个 Lucene 搜索,如果结果少于 10 个,则对概要进行第二次搜索 - 然后我在语法上合并结果。然而,这并不理想,因为我需要解决重复项,并且分页效果不佳 - 如果可能,最好在 1 中全部完成。
这可能吗?如果可以,怎么做?
(我目前正在使用 Java Lucene jar 进行开发)
这是我目前的尝试(用 Scala 编写)
// Creating the indexes
private def addDoc(w:IndexWriter , clientContent: ClientContent, contentType:String):Unit ={
val doc:Document = new Document()
doc.add(new TextField("title", clientContent.title, Field.Store.YES))
doc.add(new TextField("synopsis", clientContent.synopsis, Field.Store.YES))
doc.add(new StringField("id", clientContent.id, Field.Store.YES))
doc.add(new IntField("popularity", 100000 - clientContent.popularity.day, Field.Store.YES))
doc.add(new StringField("contentType", contentType, Field.Store.YES))
w.addDocument(doc);
}
def createIndex: Unit = {
index = new RAMDirectory()
val analyzer = new StandardAnalyzer(Version.LUCENE_43)
val config = new IndexWriterConfig(Version.LUCENE_43, analyzer)
val w = new IndexWriter(index, config)
clientApplication.shows.list.map {addDoc(w, _, "Show")}
w.close()
reader = IndexReader.open(index)
searcher = new IndexSearcher(reader)
}
// Searching by one field
def dataSearch(queryString: String, section:String, limit:Int):Array[ScoreDoc] = {
val collector = TopFieldCollector.create(
new Sort(new SortField("popularity", SortField.Type.INT, true)),
limit,false,true, true, false);
val analyzer = new StandardAnalyzer(Version.LUCENE_43)
val q = new QueryParser(Version.LUCENE_43, section, analyzer).parse(queryString+"*")
searcher.search(q, collector)
collector.topDocs().scoreDocs
}
// Searching for a query
def search(queryString:String) = {
println(s"search $queryString")
val titleResults = dataSearch(queryString, "title", limit)
if (titleResults.length < limit) {
val synopsisResults = dataSearch(queryString, "synopsis", limit - titleResults.length)
createModel(titleResults ++ synopsisResults)
}
else
createModel(titleResults)
}
可以先按分数排序,再按热度排序,大大提升title的查询。只要与标题匹配的所有字段的分数都相等,并且仅与摘要匹配的文档的分数相等,这样做就可以了:
Sort mySort = new Sort(SortField.FIELD_SCORE, new SortField("popularity", SortField.Type.INT, true));
当然,它们可能不相等。只要提升足够大,idf 就不会成为问题,但是......如果不同文档的字段长度不同,则 lengthNorm 会使分数不相等,除非你禁用了规范。 coord 因素会导致问题,因为匹配 both 字段的文档将比仅匹配标题的文档具有更高的分数。而如果一个匹配词在一个字段中出现不止一次,那么 tf 就会明显不同。
因此,您需要一种方法来简化评分,并防止所有花哨的 lucene 相关性评分逻辑获得成功。您可以使用 ConstantScoreQuery
and DisjunctionMaxQuery
.
Query titleQuery = new ConstantScoreQuery(new PrefixQuery(new Term("title", queryString)));
titleQuery.setBoost(2);
Query summaryQuery = new ConstantScoreQuery(new PrefixQuery(new Term("title", queryString)));
//Combine with a dismax, so matching both fields won't get a higher score than just the title
Query finalQuery = new DisjnctionMaxQuery(0);
finalQuery.add(titleQuery);
finalQuery.add(summaryQuery);
Sort mySort = new Sort(
SortField.FIELD_SCORE,
new SortField("popularity", SortField.Type.INT, true)
);
val collector = TopFieldCollector.create(mySort,limit,false,true,true,false);
searcher.search(finalQuery, collector);
对于您提供的代码,这将起作用,因为除了构造前缀查询外,您实际上并不需要查询解析器。不过,您也可以保留解析器。 ConstantScoreQuery
是包装器查询。您可以简单地包装从 QueryParser.parse
返回的查询:
QueryParser parser = new QueryParser(Version.LUCENE_43, "title", analyzer);
Query titleQuery = new ConstantScoreQuery(parser.parse(queryString + "*"));
titleQuery.SetBoost(2);
Query summaryQuery = new ConstantScoreQuery(parser.parse("summary:" + queryString + "*"));