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 + "*"));