【问题标题】:Getting field value in elastic search plugin against document returned by result根据结果​​返回的文档获取弹性搜索插件中的字段值
【发布时间】:2018-09-29 14:30:08
【问题描述】:

我的要求是基于模糊匹配从弹性搜索中搜索文档,然后通过比较文档的值和输入字符串来“重新评分”文档,例如如果查询返回 3 个文档(doc:1,2,3),那么为了比较常量值“Star Wars”,比较应为:

doc:1, MovieName:"Star Wars" (compare ('Star Wars','Star Wars'))
doc:2, MovieName:"Starr Warz" (compare ('Star Wars','Starr Warz'))
doc:3, MovieName:"The Star Wars" (compare ('Star Wars','The Star Wars'))

我找到了以下elasticsearch rescore插件示例并实现了它以实现上述目的。 https://github.com/elastic/elasticsearch/blob/6.2/plugins/examples/rescore/src/main/java/org/elasticsearch/example/rescore/ExampleRescoreBuilder.java

我能够在插件中传递和访问输入“星球大战”,但是在获取结果中返回的文档的 MovieName 字段的值时遇到了麻烦 (topdocs)。

我的查询:

  GET movie-idx/_search?
    {
      "query": {
        "bool": {
          "must": [
            {
              "query_string": {
                "fields": [
                  "MovieName"
                ],
                "query": "Star Wars",
                "minimum_should_match": "61%",
                "fuzziness": 1,
                "_name": "fuzzy"
              }
            }
          ]
        }
      },
      "rescore": {
        "calculateMovieScore": {
          "MovieName": "Star Wars"
        }
      }
    }

我的记分员类看起来像:

private static class DocsRescorer implements Rescorer {
        private static final DocsRescorer INSTANCE = new DocsRescorer();

        @Override
        public TopDocs rescore(TopDocs topDocs, IndexSearcher searcher, RescoreContext rescoreContext) throws IOException {
            DocRescoreContext context = (DocRescoreContext) rescoreContext;
            int end = Math.min(topDocs.scoreDocs.length, rescoreContext.getWindowSize());

            MovieScorer MovieScorer = new MovieScorerBuilder()
                    .withInputName(context.MovieName)
                    .build();

            for (int i = 0; i < end; i++) {
                String name = <get MovieName values from actual document returned by topdocs>
                float score = MovieScorer.calculateScore(name);
                topDocs.scoreDocs[i].score = score;
            }

            List<ScoreDoc> scoreDocList =  Stream.of(topDocs.scoreDocs).filter((a) -> a.score >= context.threshold).sorted(
                    (a, b) -> {
                        if (a.score > b.score) {
                            return -1;
                        }
                        if (a.score < b.score) {
                            return 1;
                        }
                        // Safe because doc ids >= 0
                        return a.doc - b.doc;
                    }
            ).collect(Collectors.toList());
            ScoreDoc[] scoreDocs = scoreDocList.toArray(new ScoreDoc[scoreDocList.size()]);
            topDocs.scoreDocs = scoreDocs;
            return topDocs;
        }

        @Override
        public Explanation explain(int topLevelDocId, IndexSearcher searcher, RescoreContext rescoreContext,
                                   Explanation sourceExplanation) throws IOException {
            DocRescoreContext context = (DocRescoreContext) rescoreContext;
            // Note that this is inaccurate because it ignores factor field
            return Explanation.match(context.factor, "test", singletonList(sourceExplanation));
        }

        @Override
        public void extractTerms(IndexSearcher searcher, RescoreContext rescoreContext, Set<Term> termsSet) {
            // Since we don't use queries there are no terms to extract.
        }
    }

我的理解是插件代码将执行一次,它将从初始查询(在这种情况下为模糊搜索)获取 topdocs,并且 for (int i = 0; i

String name = <get MovieName value from actual document returned by topdocs>

【问题讨论】:

    标签: elasticsearch lucene elasticsearch-plugin


    【解决方案1】:

    我知道这已经超过 2 年了,但我遇到了同样的问题并找到了解决方案,所以我将其发布在这里。这是为 ES 7.8.0 中的 Rescorer 插件完成的。我使用的基本示例是分组插件Link

    这是一堆我不完全理解的代码,但主要原则是您需要一个您想要获取的字段的 IFD (IndexFieldData>) 实例。在我的示例中,我只需要命中的 _id。它看起来像这样:

    1. 提前准备好 IFD 并将其传递给 RescoreContext:向扩展 RescoreContext 的类添加一个成员,以将此 IFD 保留在上下文中,我们将其称为“idField”(稍后在第 3 节中使用)。
    @Override
    public RescoreContext innerBuildContext(int windowSize, QueryShardContext queryShardContext) throws IOException {
        return new MyRescoreContext(windowSize, queryShardContext.getForField(queryShardContext.fieldMapper("_id")));
    }
    
    1. 接下来,在 Rescorer 本身中:(方法 rescore(...) )

    2.1) 首先按 scoreDoc.doc 排序

     ScoreDoc[] hits = topDocs.scoreDocs; 
     Arrays.sort(hits, Comparator.comparingInt((d) -> d.doc));
    

    2.2) 执行黑魔法(我不明白的代码)

    List<LeafReaderContext> readerContexts = searcher.getIndexReader().leaves();
    int currentReaderIx = -1;
    int currentReaderEndDoc = 0;
    LeafReaderContext currentReaderContext = null;
        
    for (int i = 0; i < end; i++) {
    ScoreDoc hit = hits[i];
        
        // find segment that contains current document 
       while (hit.doc >= currentReaderEndDoc) {  
          currentReaderIx++;
          currentReaderContext = readerContexts.get(currentReaderIx);
          currentReaderEndDoc = currentReaderContext.docBase + currentReaderContext.reader().maxDoc();
       }
    
       int docId = hit.doc - currentReaderContext.docBase;
    
       // code from section 3 goes here //
    }
    
    1. 现在,有了这个神奇的“docId”,您可以从 For 循环内的 IFD 中获取:

       SortedBinaryDocValues values = rescoreContext.idField.load(currentReaderContext).getBytesValues();
       values.advanceExact(docId);
       String id = values.nextValue().utf8ToString();
      

    在您的情况下,而不是 _id 字段,获取所需字段的 IFD,并在 For 循环内从 docId -> 字符串值创建一个 Hashmap。 然后在应用分数的同一个 For 循环中使用此映射。

    希望对大家有帮助!这种技术根本没有记录,也没有任何解释!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-07-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-04-13
      相关资源
      最近更新 更多