
# 使用Elasticsearch自定义规则排序搜索结果
通过Elasticsearch集群可以对搜索结果进行自定义规则排序。
#### 应用场景
Elasticsearch是一个高度可扩展的开源搜索和分析引擎，支持用户通过自定义规则对搜索结果排序。自定义排序允许开发者根据业务需求，定义特定的排序规则，以优化搜索结果的相关性和用户体验。该方案可以用于以下场景：
- 电子商务：根据销量、用户评价、价格等因素对商品排序。
- 内容管理：根据阅读量、发布时间对文章或博客帖子排序。
- 金融服务：根据交易金额、频率或风险评分对交易记录排序。
- 客户支持：根据工单的紧急程度或打开时间对客户请求排序。
 
#### 方案架构
通过自定义规则对搜索结果排序是通过Elasticsearch的排序API实现的。通过调用排序API查询数据，实现数据按自定义规则排序。
自定义规则查询有两种方式。
- 用**绝对好评率** 计算总分，按照总分由高到低的顺序排列出查询结果。
  ```
  总分 = 匹配得分 * （好评率 * 绝对因子）
  ```
  - 匹配得分：根据查询结果计分，内容匹配记1分，否则记0分，得分之和即为匹配得分。
  
  - 好评率：从匹配项的数据内容中获取好评率的值，一般指单条数据的评分。
  
  - 绝对因子：自定义的好评比例。
   
- 用**相对好评率** 计算总分，按照总分由高到低的顺序排列查询结果。
  ```
  总分 = 匹配得分 * （好评率 * 相对分数）
  ```
  - 匹配得分：根据查询结果计分，内容匹配记1分，否则记0分，得分之和即为匹配得分。
  
  - 好评率：从匹配项的数据内容中获取好评率的值，一般指单条数据的评分。
  
  - 相对分数：自定义一个好评率阈值，当好评率大于阈值时，返回一个自定义的相对分数；当好评率小于等于阈值时，返回另一个自定义的相对分数。通过这种方式可以避免异常好评率对查询结果的影响。
   
 
#### 方案优势
- 灵活性：自定义排序规则可以满足各种复杂的业务需求。
- 扩展性：Elasticsearch的分布式特性支持水平扩展，适应不断增长的数据量。
- 性能：Elasticsearch的优化机制确保了排序操作的高效性，即使在大规模数据集上也能保持良好的性能。
- 实时性：Elasticsearch的近实时搜索能力确保排序结果的时效性。
 
#### 前提条件
已经准备好Elasticsearch集群，且集群处于可用状态。
#### 操作步骤
![](https://support.huaweicloud.com/bestpractice-css/public_sys-resources/note_3.0-zh-cn.png)
本文的代码示例仅适用于Elasticsearch 7.x及以上版本的集群。
1. 登录Kibana进入命令执行页面。
   1. 登录[云搜索服务管理控制台](https://console.huaweicloud.com/elasticsearch/)。
   
   2. 在左侧导航栏，选择"集群管理 \> Elasticsearch"。
   
   3. 在集群列表，选择目标集群，单击操作列的"Kibana"，登录Kibana。
   
   4. 在Kibana左侧导航栏选择"Dev Tools"，进入操作页面。 控制台左侧是命令输入框，其右侧的三角形图标为执行按钮，右侧区域则显示执行结果。
      
    
2. 创建索引，并指定自定义映射来定义数据类型。 例如，数据文件"tv.json"的内容如下所示。
   ```
   {
     "tv": [
       {
         "name": "tv1",
         "description": "USB, DisplayPort",
         "vote": 0.98
       },
       {
         "name": "tv2",
         "description": "USB, HDMI",
         "vote": 0.99
       },
       {
         "name": "tv3",
         "description": "USB",
         "vote": 0.5
       },
       {
         "name": "tv4",
         "description": "USB, HDMI, DisplayPort",
         "vote": 0.7
       }
     ]
   }
   ```
   可以执行如下命令，创建索引"mall"，并指定自定义映射来定义数据类型。
   ```
   PUT /mall?pretty
   {
     "mappings": {
       "properties": {
         "name": {
           "type": "text",
           "fields": {
             "keyword": {
               "type": "keyword"
             }
           }
         },
         "description": {
           "type": "text",
           "fields": {
             "keyword": {
               "type": "keyword"
             }
           }
         },
         "vote": {
           "type": "float"
         }
       }
     }
   }
   ```
   
3. 导入数据。
   执行如下命令，将"tv.json"文件中的数据导入到"mall"索引中。
   ```
   POST /mall/_bulk?pretty
   { "index": {"_id": "1"}}
   { "name": "tv1", "description": "USB, DisplayPort", "vote": 0.98 }
   { "index": {"_id": "2"}}
   { "name": "tv2", "description": "USB, HDMI", "vote": 0.99 }
   { "index": {"_id": "3"}}
   { "name": "tv3", "description": "USB", "vote": 0.5 }
   { "index": {"_id": "4"}}
   { "name": "tv4", "description": "USB, HDMI, DisplayPort", "vote": 0.7 }
   ```
   
4. 自定义规则查询数据。分别列举了绝对好评率和相对好评率查询方式。
   假设用户想要查询有USB接口、HDMI接口、DisplayPort接口的电视机，并根据好评率计算各款电视机的总分，根据总分由高到低的顺序排列结果。
   - 用绝对好评率计算总分 总分的计算公式为"new_score = query_score \* (vote \* factor)"，执行的命令如下：
     ```
     GET /mall/_doc/_search?pretty
     {
       "query":{
         "function_score":{
           "query":{
             "bool":{
               "should":[
                 {"match": {"description": "USB"}},
                 {"match": {"description": "HDMI"}},
                 {"match": {"description": "DisplayPort"}}
               ]
             }
           },
           "field_value_factor":{
             "field":"vote",
             "factor":1
           },
           "boost_mode":"multiply",
           "max_boost":10
         }
       }
     }
     ```
     返回结果如下所示，按照总分由高到低的顺序排列查询结果。
     ```
     {
       "took" : 4,
       "timed_out" : false,
       "_shards" : {
         "total" : 1,
         "successful" : 1,
         "skipped" : 0,
         "failed" : 0
       },
       "hits" : {
         "total" : {
           "value" : 4,
           "relation" : "eq"
         },
         "max_score" : 0.8388366,
         "hits" : [
           {
             "_index" : "mall",
             "_type" : "_doc",
             "_id" : "4",
             "_score" : 0.8388366,
             "_source" : {
               "name" : "tv4",
               "description" : "USB, HDMI, DisplayPort",
               "vote" : 0.7
             }
           },
           {
             "_index" : "mall",
             "_type" : "_doc",
             "_id" : "2",
             "_score" : 0.7428025,
             "_source" : {
               "name" : "tv2",
               "description" : "USB, HDMI",
               "vote" : 0.99
             }
           },
           {
             "_index" : "mall",
             "_type" : "_doc",
             "_id" : "1",
             "_score" : 0.7352994,
             "_source" : {
               "name" : "tv1",
               "description" : "USB, DisplayPort",
               "vote" : 0.98
             }
           },
           {
             "_index" : "mall",
             "_type" : "_doc",
             "_id" : "3",
             "_score" : 0.03592815,
             "_source" : {
               "name" : "tv3",
               "description" : "USB",
               "vote" : 0.5
             }
           }
         ]
       }
     }
     ```
     
   
   - 用相对好评率计算总分。 总分的计算公式为"new_score = query_score \* inline"，本示例中设置的好评率阈值为0.8，当vote\>0.8时，inline取值为1；当vote≤0.8时，inline取值为0.5。执行命令如下：
     ```
     GET /mall/_doc/_search?pretty
     {
       "query":{
         "function_score":{
           "query":{
             "bool":{
               "should":[
                 {"match":{"description":"USB"}},
                 {"match":{"description":"HDMI"}},
                 {"match":{"description":"DisplayPort"}}
               ]
             }
           },
           "script_score": {
             "script": {
               "params": {
                 "threshold": 0.8
               },
               "inline": "if (doc[\"vote\"].value > params.threshold) {return 1;} return 0.5;"
             }
           },
           "boost_mode":"multiply",
           "max_boost":10
         }
       }
     }
     ```
     返回结果如下所示，按照总分由高到低的顺序排列查询结果。
     ```
     {
       "took" : 4,
       "timed_out" : false,
       "_shards" : {
         "total" : 1,
         "successful" : 1,
         "skipped" : 0,
         "failed" : 0
       },
       "hits" : {
         "total" : {
           "value" : 4,
           "relation" : "eq"
         },
         "max_score" : 0.75030553,
         "hits" : [
           {
             "_index" : "mall",
             "_type" : "_doc",
             "_id" : "1",
             "_score" : 0.75030553,
             "_source" : {
               "name" : "tv1",
               "description" : "USB, DisplayPort",
               "vote" : 0.98
             }
           },
           {
             "_index" : "mall",
             "_type" : "_doc",
             "_id" : "2",
             "_score" : 0.75030553,
             "_source" : {
               "name" : "tv2",
               "description" : "USB, HDMI",
               "vote" : 0.99
             }
           },
           {
             "_index" : "mall",
             "_type" : "_doc",
             "_id" : "4",
             "_score" : 0.599169,
             "_source" : {
               "name" : "tv4",
               "description" : "USB, HDMI, DisplayPort",
               "vote" : 0.7
             }
           },
           {
             "_index" : "mall",
             "_type" : "_doc",
             "_id" : "3",
             "_score" : 0.03592815,
             "_source" : {
               "name" : "tv3",
               "description" : "USB",
               "vote" : 0.5
             }
           }
         ]
       }
     }
     ```
     
     
 
