2023-09-12 - 7 搜索排序

1 查询时设置权重

用户可以使用布尔查询将多种查询组合在一起。在默认情况下,这些查询的权重都为1,也就是查询之间都是平等的。有时我们希望某些查询的权重高一些,也就是在其他条件相同的情况下,匹配该查询的文档得分更高。此时应该怎么做呢?本节将介绍的boosting查询和boost设置可以满足上述查询需求。

1.1 查询时boost参数的设置

在ES中可以通过查询的boost值对某个查询设定其权重。在默认情况下,所有查询的boost值为1。但是当设置某个查询的boost为2时,不代表匹配该查询的文档评分是原来的2倍,而是代表匹配该查询的文档得分相对于其他文档得分被提升了。例如,可以为查询A设定boost值为3,为查询B设定boost值为6,则在进行相关性计算时,查询B的权重将比查询A相对更高一些。

boost值的设置只限定在term查询和类match查询中,其他类型的查询不能使用boost设置。boost值没有特别约束,因为它代表的是一个相对值。当该值在0~1时表示对权重起负向作用,当该值大于1时表示对权重起正向作用。为方便演示,下面先创建索引:

PUT /hotel 
{ 
  "settings": { 
    "number_of_shards": 1 
  },  
 "mappings": {                           //定义字段 
    "properties": { 
      "title": { 
        "type": "text" 
      }, 
      "price": { 
        "type": "double" 
      }, 
      "full_room": { 
        "type": "boolean" 
      } 
    } 
  } 
} 

在上面的DSL中设定了索引的主分片数为1,这是为了方便计算文本的IDF值,现在向索引中写入数据:

POST /_bulk 
{"index":{"_index":"hotel","_id":"001"}} 
{"title": "文雅酒假日酒店","price": 556.00,"full_room":false} 
{"index":{"_index":"hotel","_id":"002"}} 
{"title": "金都嘉怡假日酒店","price": 337.00,"full_room":true} 
{"index":{"_index":"hotel","_id":"003"}} 
{"title": "金都欣欣酒店","price": 200.00,"full_room":true} 
{"index":{"_index":"hotel","_id":"004"}} 
{"title": "金都家至酒店","price": 500.00,"full_room":false} 
{"index":{"_index":"hotel","_id":"005"}} 
{"title": "文雅精选酒店","price": 800.00,"full_room":true}  

下面对索引进行查询,假设“金都”或者“文雅”是两个酒店的品牌,用户想查询标题中包含“金都”或者“文雅”的酒店文档:

GET /hotel/_search 
{ 
  "query": { 
    "bool": { 
      "should": [ 
        { 
          "match": {                   //查询标题匹配“金都”的文档 
            "title":{ 
              "query": "金都" 
            } 
          } 
        }, 
        { 
          "match": {                  //查询标题匹配“文雅”的文档 
            "title":{ 
              "query": "文雅" 
            } 
          } 
        } 
      ] 
    } 
  } 
} 

在默认情况下,各个子查询的boost值为1,也就是说上述的两个match查询是平等的。文档的分值等于两个match相关性分数之和。执行上述DSL后结果如下:

{"hits" : {"hits" : [                     //匹配的文档列表 
      {"_id" : "005", 
        "_score" : 1.7743526, 
        "_source" : { 
          "title" : "文雅精选酒店",} 
      },
      {"_id" : "001", 
        "_score" : 1.6631467, 
        "_source" : { 
          "title" : "文雅酒假日酒店",} 
      }, 
      {"_id" : "002", 
        "_score" : 1.0924089, 
        "_source" : { 
          "title" : "金都嘉怡酒店",} 
      }, 
      {"_id" : "003", 
        "_score" : 1.0924089, 
        "_source" : { 
          "title" : "金都欣欣酒店",} 
      }, 
      {"_id" : "004", 
        "_score" : 1.0924089, 
        "_source" : { 
          "title" : "金都家至酒店",} 
      } 
    ] 
  } 
}  

通过上述结果可以看到,“金都”品牌的酒店文档的打分相对低一些,如果想对“金都”品牌的酒店进行推广,也就是提升标题中包含“金都”这些文档的排序分值,则可以设定“金都”的match查询的boost值更高一些,例如下面的DSL:

GET /hotel/_search 
{ 
  "query": { 
    "bool": { 
      "should": [ 
        { 
          "match": { 
            "title":{ 
              "query": "金都", 
             "boost": 2                  //设置“金都”匹配的权重更高一些 
            } 
          } 
        }, 
        { 
          "match": { 
            "title":{ 
              "query": "文雅" 
            } 
          } 
        } 
      ] 
    } 
  } 
}

执行上述DSL后,ES的返回结果如下:

{"hits" : {"hits" : [                         //匹配的文档列表 
      {"_id" : "002", 
        "_score" : 2.1848178, 
        "_source" : { 
          "title" : "金都嘉怡酒店",} 
      }, 
      {"_id" : "003", 
        "_score" : 2.1848178, 
        "_source" : { 
          "title" : "金都欣欣酒店",} 
      }, 
      { 
        "_index" : "hotel", 
        "_type" : "_doc", 
        "_id" : "004", 
        "_score" : 2.1848178, 
        "_source" : { 
          "title" : "金都家至酒店",} 
      }, 
      {"_id" : "005", 
        "_score" : 1.7743526, 
        "_source" : { 
          "title" : "文雅精选酒店",} 
      }, 
      {"_id" : "001", 
        "_score" : 1.6631467, 
        "_source" : { 
          "title" : "文雅酒假日酒店",} 
      } 
    ] 
  } 
} 

如上所示,设定的boost值提升了标题中包含“金都”的文档的得分。现在我们来思考一下上述match查询的打分细节,在默认情况下,文档的boost为BM25中的k1+1,因为在默认情况下k1=1.2,所以boost=k1+1=1.2+1=2.2。当在match查询中设置boost为2时,匹配该查询文档的最终boost=(k1+1)×2=(1.2+1)×2=4.4。

title字段使用标准分析器,设置“金都”这个match查询的boost值为2后,在查询时“金都”被切分成“金”“都”,这两个切分的字在BM25查询中的最终boost值都为4.4。

因此设置match查询的boost参数可以直接影响BM25的评分机制,从而影响整体结果的相关度。更近一步说,设置boost参数为某个值后并不是将查询命中的文档分数乘以该值,而是将BM25中的boost参数乘以该数值。

在Java客户端中使用boost参数时,只需要在QueryBuilder实例中调用boost()方法即可,以下Java代码和上面的DSL在搜索结果上是等效的:

public void getBoostSearch() { 
    //创建搜索请求 
    SearchRequest searchRequest = new SearchRequest("hotel"); 
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(
    //构建“金都”的match查询 
    MatchQueryBuilder matchQueryBuilder1 = QueryBuilders.matchQuery 
("title", "金都"); 
    //设置boost值为2 
    matchQueryBuilder1.boost(2); 
    //构建“文雅”的match查询,boost使用默认值 
    MatchQueryBuilder matchQueryBuilder2 = QueryBuilders.matchQuery 
("title", "文雅"); 
    BoolQueryBuilder boolQueryBuilder=QueryBuilders.boolQuery(); 
    //将“金都”的match查询添加到布尔查询中 
    boolQueryBuilder.should(matchQueryBuilder1); 
    //将“文雅”的match查询添加到布尔查询中 
    boolQueryBuilder.should(matchQueryBuilder2); 
    searchSourceBuilder.query(boolQueryBuilder); //设置查询为布尔查询 
    searchRequest.source(searchSourceBuilder);   //设置查询请求 
   printResult(searchRequest);                   //打印搜索结果 
} 

1.2 boosting查询

虽然使用boost值可以对查询的权重进行调整,但是仅限于term查询和类match查询。有时需要调整更多类型的查询,如搜索酒店时,需要将房价低于200的酒店权重降低,此时可能需要用到range查询,但是range查询不能使用boost参数,这时可以使用ES的boosting查询进行封装

ES的boosting查询分为两部分,一部分是positive查询,代表正向查询,另一部分是negative查询,代表负向查询。可以通过negative_boost参数设置负向查询的权重系数,该值的范围为0~1。最终的文档得分为:正向匹配值+负向匹配值×negative_boost。先来看看使用原始查询时的搜索排序状态,以下DSL为搜索“金都”查询:

GET /hotel/_search 
{ 
 "query": {                //普通的match搜索 
    "match": { 
      "title": "金都" 
    } 
  } 
}  

搜索结果如下:

{"hits" : {"max_score" : 1.0924089, 
   "hits" : [               //匹配的文档列表  
      {"_id" : "002", 
        "_score" : 1.0924089, 
        "_source" : { 
          "title" : "金都嘉怡酒店", 
          "price" : 337.0, 
          "full_room" : true 
        } 
      }, 
      {"_id" : "003", 
        "_score" : 1.0924089, 
        "_source" : { 
          "title" : "金都欣欣酒店", 
          "price" : 200.0, 
          "full_room" : true 
        } 
      }, 
      {"_id" : "004", 
        "_score" : 1.0924089, 
        "_source" : { 
          "title" : "金都家至酒店", 
          "price" : 500.0, 
          "full_room" : false 
        } 
      } 
    ] 
  } 
} 

可以看到,上面的搜索结果只是按照标题相关度进行了打分,其中,标价为200元的“金都欣欣酒店”排在第二位,如果希望它排在最后该怎么做呢?下面的DSL将对房价低于200元的酒店进行降权处理:

GET /hotel/_search 
{ 
  "query": { 
    "boosting": { 
      "positive": { 
        "match": { 
          "title": { 
            "query": "金都" 
          } 
        } 
      }, 
      "negative": {            //设置负面查询 
        "range": { 
          "price": { 
            "lte": 200 
          } 
        } 
      }, 
      "negative_boost": 0.2   //设置降低的权重值 
    } 
  } 
}   

执行上述DSL后,ES搜索结果如下:

{"hits" : { 
    "total" : { 
      "value" : 3, 
      "relation" : "eq" 
    }, 
    "max_score" : 1.0924089, 
   "hits" : [                  //匹配的文档列表 
      {"_id" : "002", 
        "_score" : 1.0924089, 
        "_source" : { 
          "title" : "金都嘉怡酒店", 
          "price" : 337.0, 
          "full_room" : true 
        } 
      }, 
      {"_id" : "004", 
        "_score" : 1.0924089, 
        "_source" : { 
          "title" : "金都家至酒店", 
          "price" : 500.0, 
          "full_room" : false 
        } 
      }, 
      {"_id" : "003", 
        "_score" : 0.21848178, 
        "_source" : { 
          "title" : "金都欣欣酒店", 
          "price" : 200.0, 
          "full_room" : true 
        } 
      } 
    ] 
  } 
} 

通过上面的结果可知,对房价低于200元的酒店进行降权处理后,目标酒店已经排在了最后面。

如果在以上结果基础上要求降低满房酒店的权重该怎么做呢?我们可以将在negative中的查询进行扩展:

GET /hotel/_search 
{ 
  "query": { 
    "boosting": { 
      "positive": { 
        "match": { 
          "title": { 
            "query": "金都" 
          } 
        } 
      }, 
      "negative": {               //扩展negative查询,增加更多条件 
        "bool": { 
          "should": [ 
            { 
              "range": { 
                "price": { 
                  "lte": 200 
                } 
              } 
            }, 
            { 
              "term": { 
                "": { 
                  "value": "true" 
                } 
              } 
            } 
          ] 
        } 
      }, 
      "negative_boost": 0.2 
    } 
  } 
}  

在以上查询中,使用布尔查询将“房价低于200元”和“满房状态”的酒店封装到了一个布尔查询中然后放入negative查询中,执行上述DSL后,搜索结果如下:

{"hits" : {"hits" : [                     //匹配的文档列表 
 
 
      {"_id" : "004", 
        "_score" : 1.0924089, 
        "_source" : { 
          "title" : "金都家至酒店", 
          "price" : 500.0, 
          "full_room" : false 
        } 
      }, 
      {"_id" : "002", 
        "_score" : 0.21848178, 
        "_source" : { 
          "title" : "金都嘉怡酒店", 
          "price" : 337.0, 
          "full_room" : true 
        } 
      }, 
      {"_id" : "003", 
        "_score" : 0.21848178, 
        "_source" : { 
          "title" : "金都欣欣酒店", 
          "price" : 200.0, 
          "full_room" : true 
        } 
      } 
    ] 
  } 
}

如上所示,通过使用negative进行调权,“房价低于200元”和“满房状态”的酒店都排在了后面。

在Java客户端中使用boosting查询:

public void getBoostingSearch() { 
    //创建搜索请求 
    SearchRequest searchRequest = new SearchRequest("hotel"); 
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); 
    //构建“金都”的match查询 
    MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery 
("title", "金都"); 
    //构建价格的range查询 
    QueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("price"). 
lte("200"); 
    //构建满房的term查询 
    QueryBuilder termQueryBuilder = QueryBuilders.termQuery("full_room", 
true); 
    BoolQueryBuilder boolQueryBuilder=QueryBuilders.boolQuery(); 
    //将价格的range查询添加到布尔查询中 
    boolQueryBuilder.should(rangeQueryBuilder); 
    //将满房的term查询添加到布尔查询中 
    boolQueryBuilder.should(termQueryBuilder); 
    //构建boosting查询,将match查询作为正向查询,布尔查询作为负向查询 
    BoostingQueryBuilder boosting=QueryBuilders.boostingQuery(matchQuery 
Builder,boolQueryBuilder); 
    boosting.negativeBoost(0.2f);              //设置负向查询的权重系数 
    searchSourceBuilder.query(boosting);       //设置查询为boosting查询 
    searchRequest.source(searchSourceBuilder); //设置查询请求 
   printResult(searchRequest);                 //打印搜索结果 
} 

2 Function Score查询简介

例如,一个酒店搜索引擎,不仅需要考虑查询词和酒店名称的匹配程度,而且还需要评估酒店的好评率、地理位置和设施服务等诸多因素。ES提供了Function Score查询模式,用户可以借助该查询模式使用多种因素来影响打分逻辑。

2.1 简单函数

我们在第4章中简要介绍了在Function Score查询中可以使用random_score随机函数对文档进行打分,在Function Score查询中还提供了其他打分函数,如表6.1所示。
在这里插入图片描述
在这些函数中,比较灵活的是script_score函数,它支持用户使用脚本自定义评分函数。在函数体中,既可以使用原有的score值进行计算,也可以使用文档的某个字段值的一些运算结果来影响评分的值。下面使用script_score函数将原有评分和好评数相乘的结果作为最后的评分,示例如下:

GET /hotel/_search 
{ 
  "query": { 
    "function_score": {  
      "query": {               //定义查询条件 
        "match_all": {}        //匹配所有文档 
      }, 
      "functions": [           //可以定义多个打分函数 
        {  
          "script_score": {    //使用脚本打分 
            "script": "_score * doc[' favourable_comment'].value" 
          } 
        } 
      ] 
    } 
  } 
} 

需要注意的是,script_score子句中的结果必须大于或者等于0,不能为负数,否则,ES将会报错。以下是script_score取值为原有评分和差评数的乘积再乘以-1的查询示例:

GET /hotel/_search 
{ 
  "query": { 
    "function_score": { 
      "query": { 
        "match_all": {}     //匹配所有文档 
      }, 
      "functions": [ 
        { 
          "script_score": { 
            //乘以-1使得分为负数 
            "script": "_score * -1*doc['negative_comment'].value" 
          } 
        } 
      ] 
    } 
  } 
} 

系统报错信息如下:

{ 
  "error" : { 
    "root_cause" : [ 
     {                        //打分函数返回负数时ES报错 
        "type" : "illegal_argument_exception", 
        "reason" : "script score function must not produce negative scores,
but got: [-10.0]" 
      } 
    ], 
    "type" : "search_phase_execution_exception",}, 
 "status" : 400               //返回的状态码 
} 

另外,还可以使用params为script_score传递参数。在下面的示例中,将原有评分和评论进行相乘,然后乘以p,并将得到的结果作为最后的评分。其中,p的值是通过params中的传递参数得到的。

GET /hotel/_search 
{ 
  "query": { 
    "function_score": { 
      "query": { 
        "match_all": {}           //匹配所有文档 
      }, 
      "functions": [ 
        { 
          "script_score": { 
            "script": { 
              "params": {        //传递p参数的值为0.5 
                "p": 0.5 
              }, 
               //在打分函数的计算中使用p参数 
              "source": "_score*doc['favourable_comment'].value*params.p" 
            } 
          } 
        } 
      ] 
    } 
  } 
} 

weight函数提供的是一个系数,最终的得分等于对原有评分乘以这个系数。例如,对命中的文档原有评分乘以10:

GET /hotel/_search 
{ 
  "query": { 
    "function_score": { 
      "query": { 
        "match_all": {}            //匹配所有文档 
      }, 
      "functions": [ 
        {                          //定义weight函数 
          "weight": 10             //定义weight函数中的权重值 
        } 
      ] 
    } 
 } 
} 

random_score产生0~1的随机小数,但是不包括1。在默认情况下,该随机函数使用的随机种子为文档_id,可以通过seed参数指定随机数种子。例如,使用随机函数的简单形式对文档打分:

GET /hotel/_search 
{ 
  "query": { 
    "function_score": { 
      "query": { 
        "match_all": {}         //匹配所有文档 
      }, 
      "functions": [ 
        { 
          "random_score": {}    //使用随机函数打分 
        } 
      ] 
    } 
  } 
}

如果希望文档中的某些字段对排序产生影响,除了使用script_score函数以外,也可以使用字段值因子函数field_value_factor,该函数可以省去编写脚本代码的麻烦。在下面的例子中以原有评分和好评数相乘的结果作为最后的评分:

GET /hotel/_search 
{ 
  "query": { 
    "function_score": { 
      "query": { 
        "match_all": {}                //匹配所有文档 
      }, 
      "functions": [ 
        { 
          "field_value_factor": { 
            //定义field_value_factor中的field参数为字段favourable_comment, 则ES将原有分数乘以字段favourable_comment的值作为文档最终得分 
            "field": "favourable_comment" 
          } 
        } 
      ] 
    } 
  } 
}

在field_value_factor中还可以使用函数进一步对字段的因子进行处理。例如使用原有评分和好评数的平方根进行相乘的结果作为最后的评分:

GET /hotel/_search 
{ 
  "query": { 
    "function_score": { 
      "query": { 
        "match_all": {} 
      }, 
      "functions": [ 
        { 
          "field_value_factor": { 
            "field": "favourable_comment", 
            //使用原有评分和好评数的平方根进行相乘的结果作为最后的评分 
            "modifier": "sqrt" 
          } 
        } 
      ] 
    } 
  } 
}

2.2 函数计算关系

如果在Function Score的functions子句中有多个函数,则可以使用score_mode参数定义各个函数值之间的计算关系,当前支持的计算关系如表6.2所示。
在这里插入图片描述
score_mode的默认值为multiply,即最终的分数默认取各个函数的值相乘的结果。在下面的示例中score_mode取各个值的加和值:

GET /hotel/_search 
{ 
  "query": { 
    "function_score": { 
      "query": { 
        "match_all": {} 
      }, 
      "score_mode": "sum",   //设置score_mode为sum,则最终得分取各个值的加和值 
      "functions": [ 
        { 
          "field_value_factor": { 
            "field": "favourable_comment", 
            "modifier": "sqrt" 
          }, 
          "random_score": {} 
        } 
      ] 
    } 
  } 
}

2.3 衰减函数

在对文档进行打分时,希望在某个值域附近进行衰减打分。例如当搜索酒店时,酒店距离当前位置越近越好。假定距离当前位置1km范围内的酒店都可以接受,如果使用过滤器将超过1km的酒店排除掉,这种做法未免有些“生硬”。假设一个酒店距离当前位置刚好是1.1km,其好评度也不错,那么是可以考虑一下该酒店的。所以我们希望酒店最好距离当前位置在1km范围内,如果超过1km,酒店的评分应该随着距离的增大有一个明显的下降趋势。

为了解决这类问题,可以在Function Score查询中使用衰减函数。ES提供了3个衰减函数,分别为gauss、linear和exp,这3个函数的区别主要是衰减曲线形状不同,但是它们的用法和参数设置都是一样的。如图6.4所示为以年龄字段为例展示的这3个函数的衰减曲线图像。
在这里插入图片描述
衰减函数可以用于数值型、日期型和地理位置型数据,需要用户设置一个中心值,如果实际值偏离中心值,无论大于中心值还是小于中心值,文档的分数都将降低。

由图6.4可知,gauss图像有点类似于钟摆,起初,其衰减值随着年龄的增大缓慢增大,然后到达某个区域后急速增大,直到到达某个阈值后又急速减小,最后又缓慢减小;linear的函数曲线是一条直线,其衰减值随着年龄的增大而线性增大,直到到达某个阈值后随着年龄的增大而线性减小;exp是一种指数衰减,它的衰减速率比gauss要激烈一些。

使用衰减函数时,可以设定如下参数:
·origin:用于设定计算距离的原点,该参数的值必须和字段类型相对应。
·offset:用于设定距离原点多远范围内的数据将享有和原点一样的衰减值,其默认值为0。
·scale:衰减曲线的一个锚点,即定义到该点的值,其衰减的值为某个值(即为decay的值)。这个锚点横坐标值的定义为原点+offset+scale,纵坐标为decay参数的值。
·decay:和scale配套使用,用于设定锚点的纵坐标,即衰减值,其默认值为0.5。

下面用一个酒店搜索的实例来介绍衰减函数的使用,先来建立酒店索引:

PUT /hotel 
{ 
  "mappings": { 
    "properties": { 
      "title": {              //定义title字段和类型 
        "type": "text" 
      }, 
      "location": {           //定义location字段和类型 
        "type": "geo_point" 
      } 
    } 
  } 
}

为方便介绍,在索引中写入一些酒店数据,这些酒店都是北京天安门附近的酒店:

POST /_bulk 
{"index":{"_index":"hotel","_id":"001"}} 
{"title": "大前门瀚海酒店","location": {"lat": 39.905194, "lon": 116.400029}} 
{"index":{"_index":"hotel","_id":"002"}} 
{"title": "磁器口美乐酒店","location": {"lat": 39.895314, "lon": 116.426241}} 
{"index":{"_index":"hotel","_id":"003"}} 
{"title": "天坛望都酒店","location": {"lat": 39.88892, "lon": 116.428415}} 
{"index":{"_index":"hotel","_id":"004"}} 
{"title": "南海公园酒店","location": {"lat": 39.918034, "lon": 116.395932}} 
{"index":{"_index":"hotel","_id":"005"}} 
{"title": "国贸天地酒店","location": {"lat": 39.914423, "lon": 116.462227}}

假设当前位置是天安门,经纬度坐标为[116.4039,39.915143],需求为搜索附近5km内的酒店,其中最佳距离是1km,超过1km的酒店打分需要按照距离进行衰减,其中3km的时候酒店衰减得分为0.4,则搜索的DSL如下:

GET /hotel/_search 
{ 
  "query": { 
    "function_score": { 
      "query": {                     //设置搜索范围为距离中心点5km 
        "geo_distance":{ 
          "distance":"5km", 
          "location":{ "lat": 39.915143, "lon": 116.4039 } 
        } 
      },  
      "functions": [ 
        { 
          "gauss": {                //设置衰减函数为gauss 
            "location": {  
              "origin": { "lat": 39.915143, "lon": 116.4039 }, 
              //最佳距离为1km,在1km范围内的所有酒店得分均相同 
              "offset": "1km", 
              "scale":  "2km",      //设定固定的衰减距离 
              "decay": 0.4          //设置距离中心点3km时的衰减得分为0.4 
            }        
          } 
        } 
      ] 
    } 
  } 
}

执行上述DSL,搜索结果如下:

{"hits" : {"hits" : [                          //返回的文档列表 
      {"_id" : "004", 
        "_score" : 1.0, 
        "_source" : {"title" : "南海公园酒店","location" : {}} 
      }, 
      {"_id" : "001", 
        "_score" : 0.99454683, 
        "_source" : {"title" : "大前门瀚海酒店","location" : {}} 
      }, 
      {"_id" : "002", 
        "_score" : 0.43195635, 
        "_source" : {"title" : "磁器口美乐酒店","location" : {}} 
      }, 
      {"_id" : "003", 
        "_score" : 0.21555501, 
        "_source" : {"title" : "天坛望都酒店","location" : {}}}, 
      {"_id" : "005", 
        "_score" : 0.026788887, 
        "_source" : {"title" : "国贸天地酒店","location" : {}} 
      } 
    ] 
  } 
}

通过以上结果大体上可以看出一些规律:距离天安门越近的酒店得分越高,反之则得分越低。为了能直观地看到更细粒度的结果,需要给出实际距离,因此我们使用普通的距离查询,并且将查询结果直接按照距离降序排列:

GET /hotel/_search 
{ 
  "query": { 
   "geo_distance": {                       //在距离中心点5km范围内搜索酒店 
      "distance": "5km", 
      "location": { 
        "lat": 39.915143, 
        "lon": 116.4039 
      } 
    } 
  }, 
  "sort": [ 
    { 
      "_geo_distance": {                  //按照与中心点的距离进行排序 
        "location": { 
          "lat": 39.915143, 
          "lon": 116.4039 
        }, 
        "unit": "km",                    //排序使用的距离计量单位 
        "order": "asc"                   //升序排列 
      } 
    } 
  ] 
}

执行上述DSL,ES的返回结果如下:

{"hits":{"hits":[                            //返回的文档列表 
            {"_id":"004", 
                "_score":null, 
                "_source":{"title":"南海公园酒店","location":{}}, 
                "sort":[0.7517456062542669] //文档的排序值:酒店与中心点的距离 
            }, 
            {"_id":"001", 
                "_score":null, 
                "_source":{ "title":"大前门瀚海酒店","location":{}}, 
                "sort":[1.154501237598761]   //文档的排序值:酒店与中心点的距离 
            }, 
            { 
                "_index":"hotel", 
                "_type":"_doc", 
                "_id":"002", 
                "_score":null, 
                "_source":{"title":"磁器口美乐酒店","location":{}}, 
                "sort":[2.91428143818939]  //文档的排序值:酒店与中心点的距离 
            }, 
            {"_id":"003", 
                "_score":null, 
                "_source":{"title":"天坛望都酒店","location":{}}, 
                "sort":[3.5882267893003497]   //文档的排序值:酒店与中心点的距离 
            }, 
            {"_id":"005", 
                "_score":null, 
                "_source":{"title":"国贸天地酒店","location":{}}, 
                "sort":[4.975151875412991]   //文档的排序值:酒店与中心点的距离 
            } 
        ] 
    } 
}

将上面的两个结果结合起来看,南海公园酒店距离天安门小于1km范围内,符合offset值的设定,因此其得分为1;大前门瀚海酒店距离天安门在1km和2km之间,并且非常接近1km,因此其得分虽然得到衰减,但只是轻微衰减到0.99左右;磁器口美乐酒店距离天安门2.91km,该值已经非常接近锚点值,因此其得分为0.43左右,更加接近decay的值(其值为0.4);天坛望都酒店和天安门的距离为3.5km,已经超过3km,因此其得分迅速衰减到0.22左右;最后的国贸天地酒店,它和天安门的距离已经接近5km,因此其得分急速衰减到0.03左右。

3 Script Score查询简介

现在让我们进入灵活度最高的排序打分世界!ES提供的Script Score查询可以以编写脚本的方式对文档进行灵活打分,以实现自定义干预结果排名的目的。Script Score默认的脚本语言为Painless,在Painless中可以访问文档字段,也可以使用ES内置的函数,甚至可以通过给脚本传递参数这种方式联通内部和外部数据。本节将详细介绍Script Score中Painless脚本的使用,并且每节都会通过实例进行演示。

3.1 Painless简介

Painless语言是一种专门用于ES中的脚本语言,它使用了类似于Groovy的语法。ES使用了沙箱技术运行Painless,且在Painless中没有任何网络连接的功能,因此它在安全性方面是有保障的。Painless是被编译成JVM字节码后运行的,它从语法上看是Java的子集,因此它又是一种简单高效的脚本语言。

1.变量

变量在使用之前必须先进行声明,其声明方式和Java保持一致。如果变量在声明时没有指定值,则使用变量类型对应的默认值。

2.数据类型

Painless支持的原始类型有:byte、short、char、int、long、float、double、boolean。可以按照Java的方式声明它们,例如下面的代码:

int i = 0;             //声明int类型变量 
boolean t = true;      //声明boolean类型变量 
double s;              //声明double类型变量

在Painless中也可以使用引用类型,可以使用new关键字对引用类型进行初始化,例如下面的代码:

List l = new ArrayList();  

字符串类型可以直接使用。例如:

 String a = "abcde"; 

像引用类型一样,数组是使用new关键字分配的:

int[] a=new int[3];         //声明数组类型变量并初始化 
a[0]=1; 
a[1]=2; 
a[2]=3;   

当然,数组的大小也可以是隐式的:

int[]x=new int[]{1,2,3};  

Painless还支持使用def对动态类型的变量进行声明,它的作用是在声明时不指定类型而是在运行时判断数据类型,例如下面的代码:

def x=5;                    //根据运行时判断变量类型 
def y="abc";  
3.条件和循环

在Painless中条件语句的使用和大多数语言是一样的,其支持使用if语句对条件进行判断,但是不支持else if或者switch语句,if语句中的条件值为boolean型。如果引用文档中的字段,编写代码时需要注意判断字段为空的情况,在后面的内容中将会介绍如何在Painless脚本中使用文档字段值。以下代码为if条件判断:

def a=10; 
if(a>8){                     //条件判断}  

Painless支持for循环、while循环和do…while循环,循环内的条件和if相同,下面的代码演示了for循环的一种使用方法:

def result=0; 
for(def a=0;a<10;a++){       //for循环 
    result=a+1; 
}

3.2 在Script Score中使用Painless

在Script Score查询中可以使用Painless脚本进行打分脚本的开发,脚本代码主体放在参数source的值中,注意,Script Score查询中的脚本代码必须有返回值并且类型为数值型,如果没有返回值,则Script Score查询默认返回0。

下面定义酒店索引的结构如下:

PUT /hotel 
{ 
  "mappings": { 
    "properties": { 
      "title": {                      //定义title字段类型为text 
        "type": "text" 
      }, 
      "price": {                     //定义price字段类型为double 
        "type": "double" 
      }, 
      "create_time": {               //定义create_time字段类型为date 
        "type": "date" 
      }, 
      "full_room": {                 //定义full_room字段类型为boolean 
        "type": "boolean" 
      }, 
      "location": {                  //定义location字段类型为geo_point 
        "type": "geo_point" 
      }, 
      "doc_weight": {                //定义doc_weight字段类型为integer 
        "type": "integer" 
      }, 
      "tags": {                      //定义tags字段类型为keyword 
        "type": "keyword" 
      }, 
      "comment_info": {              //定义comment_info字段类型为object 
        "properties": { 
          "favourable_comment": {    //定义favourable_comment字段类型为integer 
            "type": "integer" 
          }, 
          "negative_comment": {      //定义negative_comment字段类型为integer 
            "type": "integer" 
          } 
        } 
      }, 
      "hotel_vector":{               //定义hotel_vector字段类型为dense_vector 
        "type": "dense_vector", 
        "dims":5                     //定义向量的维度为5 
      } 
    } 
  } 
}

为方便演示,向酒店索引中新增如下数据:

POST /_bulk 
{"index":{"_index":"hotel","_id":"001"}} 
{"title": "文雅酒假日酒店","price": 556.00,"create_time":"20200418120000", "full_room":false,"location":{"lat": 36.083078,"lon": 120.37566},"doc_ weight":30,"tags":["wifi","小型电影院"],"comment_info":{"favourable_comment": 20,"negative_comment":10},"hotel_vector":[0,3.2,5.8,1.2,0]} 
{"index":{"_index":"hotel","_id":"002"}} 
{"title": "金都嘉怡假日酒店","price": 337.00,"create_time":"20210315200000", "full_room":false,"location":{"lat": 39.915153,"lon": 116.4030},"doc_ weight":10,"tags":["wifi","免费早餐"],"comment_info":{"favourable_comment": 20,"negative_comment":10},"hotel_vector":[0.7,9.2,5.3,1.2,12.3]} 
{"index":{"_index":"hotel","_id":"003"}} 
{"title": "金都欣欣酒店","price": 200.00,"create_time":"20210509160000", "full_room":true,"location":{"lat": 39.186555,"lon": 117.162007},"doc_ weight":10,"tags":["会议厅","免费车位"],"comment_info":{"favourable_comment": 20,"negative_comment":10},"hotel_vector":[6,3.2,0.4,9.3,0]} 
{"index":{"_index":"hotel","_id":"004"}} 
{"title": "金都家至酒店","price": 500.00,"create_time":"20210218080000", "full_room":true,"location":{"lat": 39.915343,"lon": 116.4239},"doc_weight": 50,"tags":["wifi","免费车位"],"hotel_vector":[0.7,3.2,5.1,2.9,0.1]} 
{"index":{"_index":"hotel","_id":"005"}} 
{"title": "文雅精选酒店","price": 800.00,"create_time":"20210101080000", "full_room":true,"location":{"lat": 39.918229,"lon": 116.422011},"doc_weight": 70,"tags":["wifi","充电车位"],"comment_info":{"favourable_comment":20, "negative_comment":10},"hotel_vector":[12.1,5.2,5.1,9.2,4.5]}

以下代码演示了使用脚本代码进行打分的基本方法:

GET /hotel/_search
{
  "query": {
    "script_score": {
      "query": {
        "match": {
          "title": "金都"
        }
      },
      "script": {
        "source":"""
        def a = 1;
        def b = 2;
        return a + b;
        """
      }
    }
  }
}
{
  "took" : 5,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 3,
      "relation" : "eq"
    },
    "max_score" : 3.0,
    "hits" : [
      {
        "_index" : "hotel",
        "_type" : "_doc",
        "_id" : "002",
        "_score" : 3.0,
        "_source" : {
          "title" : "金都嘉怡假日酒店",
          "price" : 337.0,
          "create_time" : "20210315200000",
          "full_room" : false,
          "location" : {
            "lat" : 39.915153,
            "lon" : 116.403
          },
          "doc_ weight" : 10,
          "tags" : [
            "wifi",
            "免费早餐"
          ],
          "comment_info" : {
            "favourable_comment" : 20,
            "negative_comment" : 10
          },
          "hotel_vector" : [
            0.7,
            9.2,
            5.3,
            1.2,
            12.3
          ]
        }
      },
      {
        "_index" : "hotel",
        "_type" : "_doc",
        "_id" : "003",
        "_score" : 3.0,
        "_source" : {
          "title" : "金都欣欣酒店",
          "price" : 200.0,
          "create_time" : "20210509160000",
          "full_room" : true,
          "location" : {
            "lat" : 39.186555,
            "lon" : 117.162007
          },
          "doc_ weight" : 10,
          "tags" : [
            "会议厅",
            "免费车位"
          ],
          "comment_info" : {
            "favourable_comment" : 20,
            "negative_comment" : 10
          },
          "hotel_vector" : [
            6,
            3.2,
            0.4,
            9.3,
            0
          ]
        }
      },
      {
        "_index" : "hotel",
        "_type" : "_doc",
        "_id" : "004",
        "_score" : 3.0,
        "_source" : {
          "title" : "金都家至酒店",
          "price" : 500.0,
          "create_time" : "20210218080000",
          "full_room" : true,
          "location" : {
            "lat" : 39.915343,
            "lon" : 116.4239
          },
          "doc_weight" : 50,
          "tags" : [
            "wifi",
            "免费车位"
          ],
          "hotel_vector" : [
            0.7,
            3.2,
            5.1,
            2.9,
            0.1
          ]
        }
      }
    ]
  }
}

从上述结果中可以看出,由于当前Script Score的代码没有返回值,所以ES默认返回0,所有的文档得分都为0。

3.3 使用文档数据

如果字段属于基本数据类型,则可以通过params._source.$field_name获取字段的值。例如,在酒店索引中,price字段为double类型,weight字段为integer类型,以下DSL演示了这两个字段的使用:

GET /hotel/_search
{
  "query": {
    "script_score": {
      "query": {
        "match": {
          "title": "金都"
        }
      },
      "script": {
        "source": """
        if(params._source.price>23 && params._source.doc_weight != null){             
            return params._source.doc_weight;      
          } 
          return 0; 
        """
      }
    }
  }
} 

也可以使用doc['$field']来引用字段,使用doc['$field'].value引用字段的值。例如,下面的DSL和上面的DSL的效果是相同的:

GET /hotel/_search 
{ 
  "query": { 
    "script_score": { 
      "query": { 
        "match": { "title": "北京" } 
      }, 
      "script": { 
        "source": """ 
          if(doc['price']!=null){ 
            return doc['price'].value;        //另一种引用字段值的方式 
          } 
          return 0; 
        """ 
      } 
    } 
  } 
} 

当字段类型为数组时,可以直接使用for循环遍历数组中的元素,请看以下DSL:

GET /hotel/_search 
{ 
  "query": { 
    "script_score": {
      "query": { 
        "match": { "title": "金都" } 
      }, 
      "script": { 
        "source": """ 
          for(def tag:params._source.tags){    //遍历数组字段 
           if("wifi"==tag){ 
            return 1; 
           } 
          } 
          return 0; 
        """ 
      } 
    } 
  } 
}  

如果需要判断数组长度,则可以使用length属性,例如:

GET /hotel/_search 
{ 
  "query": { 
    "script_score": { 
      "query": { 
        "match": { "title": "金都" } 
      }, 
      "script": { 
        "source": """ 
          if(params._source.tags.length>1){    //判断数组长度 
           return 1; 
          } 
          return 0; 
        """ 
      } 
    } 
  } 
} 

在访问object类型字段中的值时,除了使用“.”操作符引用该object类型的字段外,对其他字段的访问与访问索引的普通字段类似。例如,酒店评论中的好评数据,可以使用params._source.comment_info[‘favourable_comment’]来引用,以下DSL将评论数作为酒店的分值返回:

GET /hotel/_search 
{ 
  "query": { 
    "script_score": { 
      "query": { 
        "match": { "title": "金都" } 
      }, 
      "script": { 
        "source": """ 
        def comment=0; 
        if(params._source.comment_info!=null){ 
          if(params._source.comment_info.containsKey('favourable_comment')){ 
            //引用对象类型字段中的数据 
            comment+=params._source.comment_info['favourable_comment']; 
          } 
          if(params._source.comment_info.containsKey('negative_comment')){ 
            comment+=params._source.comment_info['negative_comment']; 
          } 
        } 
        return comment; 
        """ 
      } 
    } 
  } 
}  

在使用match匹配搜索时,ES会对文档进行BM25算法打分,尽管BM25很好地完成了评分/相关性,但有时需要根据业务需求在原有评分的基础上对相关性进行干预。可以用_score直接获取BM25算法的打分数值,请看以下示例代码:

GET /hotel/_search 
{ 
  "query": { 
    "script_score": { 
      "query": { 
        "match": { "title": "金都" } 
      }, 
      "script": { 
        "source": """ 
        return  _score*params._source.doc_weight;   //使用文档原始评分 
        """ 
      } 
    } 
  } 
}   

在上述打分代码中,ES将文本匹配分乘以文档的权重作为文档的最终分数返回。

3.4 向脚本传参

前面介绍到,Painless不提供任何网络访问的功能,假设有一部分文档数据存储在Redis中,应该如何传递数据呢?答案就是向Painless传参。假设我们已经通过Java客户端连接Redis获取到了某个特定搜索应该设定的权重值,那么在索引中搜索时,可以通过params关键字定义参数名称并设置其值,在代码中通过params['$para']这种形式进行引用,以下示例演示了使用Params关键字传递参数的方法:

GET /hotel/_search 
{ 
  "query": { 
    "script_score": { 
      "query": { 
        "match": { 
          "title": "金都" 
        } 
      }, 
      "script": { 
        "source": """ 
          return params['query_weight'];         //引用传递的参数值 
        """, 
       "params": {                               //向脚本传参 
          "query_weight": 10 
        } 
      } 
    } 
  } 
} 

在上面的代码中,首先将query_weight参数传递给查询,然后在Painless代码中使用params['query_weight']进行参数的获取,此时就完成了数据的传递。当然,如果有多个参数,可以在params中进行定义,请看以下演示代码:

GET /hotel/_search 
{ 
  "query": { 
    "script_score": { 
      "query": { 
        "match": { 
          "title": "金都" 
        } 
      }, 
      "script": { 
        "source": """ 
        def a=1;def b=2; 
        if(a+b>2){ 
          return params['query_weight1']; 
        } 
        return params['query_weight2']; 
        """, 
       "params": {                              //传递多个参数 
          "query_weight1": 10, 
          "query_weight2": 20 
        } 
      } 
    } 
  } 
} 

3.5 在Script Score中使用函数

在学习Function Score查询时,我们知道在其中可以使用一些ES内置的预定义函数进行打分干预。同样,在Script Score中也可以使用这些函数,本节将介绍一些实际应用中的常用函数。

1.使用saturation函数

saturation,顾名思义,它是计算饱和度的函数,其实相当于计算占比,即saturation(value,k)=value/(k+value),请看如下示例:

return saturation(params._source.weight,10);  

以上代码中,文档的分值为酒店的好评率。

2.使用sigmoid函数

sigmoid函数在处理数值型数据时将其值的变换值映射到0~1,它的计算公式如下:
在这里插入图片描述
在这里插入图片描述
在使用Function Score时,sigmoid函数可以对某个字段的数据进行相应的处理,以下代码直接返回调用sigmoid函数处理doc_weight字段后的值:

GET /hotel/_search 
{ 
  "query": { 
    "script_score": { 
      "query": { 
        "match": { "title": "金都" } 
      }, 
      "script": { 
        "source": """ 
        return  sigmoid(params._source.doc_weight,2,1); //使用sigmoid函数 
        """ 
      } 
    } 
  } 
} 
3.使用随机函数

在使用ES的搜索结果时,如果希望给不同用户推荐的商品排序是不同的,可以使用随机函数对商品的打分进行控制。Script Score中的randomScore函数可以产生0~1的小数(不包含边界值),其使用方式为randomScore(<seed>,<fieldName>),其中,seed为随机数种子,fieldName为非必传参数,这时ES将使用Lucene文档的ID值作为该参数的值。在一般情况下,seed参数是由外部传递进来的,以下为使用随机函数的示例:

GET /hotel/_search 
{ 
  "query": { 
    "script_score": { 
      "query": { 
        "match": { 
          "title": "金都" 
        } 
      }, 
      "script": { 
        "source": """ 
        return randomScore(params.uuidHash);     //使用随机函数 
          """, 
        "params": { 
          "uuidHash": 102322 
        } 
      } 
    } 
  } 
} 

在上面的代码中,每次执行搜索时都会给脚本代码传递不同的用户ID,对应的参数名称为uid,脚本代码会根据不同的uid产生不同的随机值,因此每个用户的查询文档的排序就不同。

等等函数。。。。不做过多介绍

3.6 在Java客户端中使用Script Score

public void getScriptScoreQuery() { 
     MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery 
("title", "金都"); 
     //编写脚本代码 
     String scoreScript = 
             "int weight=10;\n" + 
              "def random= randomScore(params.uuidHash);\n" + 
              " return weight*random;"; 
     Map paraMap = new HashMap(); 
     paraMap.put("uuidHash", 234537);    //设置传递到脚本的参数 
     //创建脚本对象 
     Script script = new Script(Script.DEFAULT_SCRIPT_TYPE, "painless",  
scoreScript, paraMap); 
     //创建ScriptScore查询builder 
     ScriptScoreQueryBuilder scriptScoreQueryBuilder = QueryBuilders. 
scriptScoreQuery(matchQueryBuilder, script); 
     SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); 
     searchSourceBuilder.query(scriptScoreQueryBuilder); 
     //创建搜索请求 
     SearchRequest searchRequest = new SearchRequest("hotel"); 
     searchRequest.source(searchSourceBuilder);    //设置查询请求 
    printResult(searchRequest);                    //打印搜索结果 
} 

4 二次打分

前面介绍的搜索打分都是针对整个匹配结果集的,如果一个搜索匹配了几十万个文档,对这些文档使用Function Score或者Script Score查询进行打分是非常耗时的,整个排序性能大打折扣。针对这种情况,ES提供了Query Rescore功能作为折中方案,它支持只针对返回文档的一部分文档进行打分。

Query Rescore工作的阶段是在原始查询打分之后,它支持对打分后Top-N的文档集合执行第二次查询和打分。通过设置window_size参数可以控制在每个分片上进行二次打分查询的文档数量,在默认情况下window_size的值为10。在默认情况下,文档的最终得分等于原查询和rescore查询的分数之和。当然,还可以使用参数对这两部分的权重进行控制,后面将结合实例介绍这部分内容。

现在有一个比较简单的查询:查询价格大于300元的酒店。DSL如下:

GET /hotel/_search 
{ 
  "query": { 
   "range": {                     //使用range查询 
      "price": { 
        "gte": 300 
      } 
    } 
  } 
}  

查询结果如下:

{"hits":{ 
        "total":{ 
            "value":4, 
            "relation":"eq" 
        }, 
        "max_score":"1.0", 
        "hits":[                  //返回的文档列表 
            {"_id":"001", 
                "_score":"1.0", 
                "_source":{ 
                    "title":"文雅酒假日酒店",} 
            }, 
            {"_id":"002", 
                "_score":"1.0", 
                "_source":{ 
                    "title":"金都嘉怡假日酒店",} 
            }, 
            {"_id":"004", 
                "_score":"1.0", 
                "_source":{ 
                    "title":"金都家至酒店", 
                    "price":"500.0",} 
            }, 
            {"_id":"005", 
                "_score":"1.0", 
                "_source":{ 
                    "title":"文雅精选酒店",} 
            } 
        ] 
    } 
}

从结果中可以看到,索引中有5个文档,匹配的文档数为4。因为使用的是范围查询,所以匹配的文档得分都为1。如果想提升在上述排序中前两个名称中包含“金都”的酒店文档排名,而这两个目标酒店的位置分别为1和3,当前的索引主分片数为1,那么应该设置window_size的值为3,使用二次打分对查询进行扩展的DSL如下:

 
GET /hotel/_search 
{ 
  "query": { 
   "range": {                      //使用range查询 
      "price": { 
        "gte": 300 
      } 
    } 
  }, 
  "rescore": { 
    "query": { 
      "rescore_query": {           //对返回的文档进行二次打分 
        "match": { 
          "title": "金都" 
        } 
      } 
    },   
    "window_size": 3               //对每个分片的前3个文档进行二次打分 
  } 
}  

在上面的DSL中,二次打分使用rescore进行封装,在rescore中可以设置二次打分的查询query和window_size,window_size设置为3意味着对每个分片的前3个文档进行二次打分。执行上述DSL的结果如下:

{"hits":{ 
        "total":{ 
            "value":4, 
            "relation":"eq" 
        }, 
        "max_score":2.1196322, 
        "hits":[                     //返回的文档列表 
            {"_id":"004", 
                "_score":2.1196322, 
                "_source":{ 
                    "title":"金都家至酒店",} 
            }, 
            {"_id":"002", 
                "_score":1.9919175, 
                "_source":{ 
                    "title":"金都嘉怡假日酒店",} 
            }, 
            {"_id":"001", 
                "_score":"1.0", 
                "_source":{ 
                    "title":"文雅酒假日酒店",} 
            }, 
            {"_id":"005", 
                "_score":"1.0", 
                "_source":{ 
                    "title":"文雅精选酒店",} 
            } 
        ] 
    } 
} 

通过对比rescore前后的结果可以看到,原有的文档002和文档004分别排在第2和第3的位置,并且得分都是1。在rescore的查询中对TOP3且标题含有“金都”的文档进行了加分操作,因此文档002和文档004的得分得到了提升。因为文档004的标题更短,所以它的分数相对更高一些,处在第一个位置,文档002处在第二个位置。

在默认情况下,当存在二次打分时,文档的得分为:原始查询分数+二次打分分数。而用户可以为这两部分的分数设置权重,所以文档得分为:原始查询分数×原始查询权重+二次打分分数×二次打分权重。可以分别设置query_weight和rescore_query_weight,为原始查询权重和二次打分权重赋值,例如下面的DSL设置原始查询权重为0.6,二次打分权重为1.7:

GET /hotel/_search 
{ 
  "query": { 
    "range": { 
      "price": { 
        "gte": 300 
      } 
    } 
  }, 
  "rescore": { 
    "query": { 
      "rescore_query": { 
        "match": { 
          "title": "金都" 
        } 
      }, 
      "query_weight": 0.6,          //设置匹配打分权重 
      "rescore_query_weight": 1.7   //设置二次打分权重 
    }, 
    "window_size": 3 
  } 
} 
public void getRescoreQuery(){ 
    //构建原始的range查询 
    QueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("price"). 
gte("300"); 
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); 
    searchSourceBuilder.query(rangeQueryBuilder); //添加原始查询Builder 
    //构建二次打分的查询 
    MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery 
("title", "金都"); 
    //构建二次打分Builder 
    QueryRescorerBuilder queryRescorerBuilder = new QueryRescorerBuilder 
(matchQueryBuilder); 
    queryRescorerBuilder.setQueryWeight(0.6f);        //设置原始打分权重 
    queryRescorerBuilder.setRescoreQueryWeight(1.7f); //设置二次打分权重 
    queryRescorerBuilder.windowSize(3); //设置每个分片参加二次打分文档的个数 
    //添加二次打分Builder 
    searchSourceBuilder.addRescorer(queryRescorerBuilder); 
    //创建搜索请求 
    SearchRequest searchRequest = new SearchRequest("hotel"); 
    searchRequest.source(searchSourceBuilder);   //设置查询请求 
   printResult(searchRequest);                   //打印搜索结果 
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值