Elasticsearch复杂排序(非script方式)

问题遇到的现象和发生背景

B2B平台涉及到根据不同的会员进行排序,存在Elasticsearch的数据结构大概是这样:

"id":1,
"price":200,
"unitPriceStrategyList" : [
  {  
    "id" : 27,
    "memberPrice" : 500.0,
    "commodityMemberList" : [
      {
        "memberId" : 157,
        "memberRoleId" : 8
      },
      {
        "memberId" : 300,
        "memberRoleId" : 4
      }
    ],
    "isEnable" : true
  },
  {  
    "id" : 28,
    "memberPrice" : 100.0,
    "commodityMemberList" : [
      {
        "memberId" : 152,
        "memberRoleId" : 8
      },
      {
        "memberId" : 298,
    "memberRoleId" : 4
      }
    ],
    "isEnable" : true
  }
]

根据当前登录人,比如是:memberId=157,memberRoleId=8的人进来,就看到500,如果是memberId=152,memberRoleId=8的人进来,就看到100,其他人不在unitPriceStrategyList.commodityMemberList里面的就看到外面的price价格,也就是200,然后根据看到的价格进行升序和降序排序

尝试过的解决方法

用script方式是可以实现这个需求,但是性能太差了,在生产环境压根就不能用

我想要达到的结果

通过Elasticsearch本身的聚合或者重打分实现上面的需求

该回答引用GPTᴼᴾᴱᴺᴬᴵ
根据您的需求,您可以考虑使用Elasticsearch中的nested数据类型来存储unitPriceStrategyList中的嵌套结构,以便查询时能够准确匹配。建议将unitPriceStrategyList中的数据展开,每一条数据表示一种商品价格策略,以方便查询。例如,将数据转换成以下格式:

"id":1,
"price":200,
"unitPriceStrategyList" : [
  {  
    "id" : 27,
    "memberId" : 157,
    "memberRoleId" : 8,
    "memberPrice" : 500.0,
    "isEnable" : true
  },
  {  
    "id" : 27,
    "memberId" : 300,
    "memberRoleId" : 4,
    "memberPrice" : 500.0,
    "isEnable" : true
  },
  {  
    "id" : 28,
    "memberId" : 152,
    "memberRoleId" : 8,
    "memberPrice" : 100.0,
    "isEnable" : true
  },
  {  
    "id" : 28,
    "memberId" : 298,
    "memberRoleId" : 4,
    "memberPrice" : 100.0,
    "isEnable" : true
  }
]


接下来,您可以使用nested数据类型来定义unitPriceStrategyList的mapping,如下所示:

PUT /my_index
{
  "mappings": {
    "properties": {
      "id": {
        "type": "integer"
      },
      "price": {
        "type": "double"
      },
      "unitPriceStrategyList": {
        "type": "nested",
        "properties": {
          "id": {
            "type": "integer"
          },
          "memberId": {
            "type": "integer"
          },
          "memberRoleId": {
            "type": "integer"
          },
          "memberPrice": {
            "type": "double"
          },
          "isEnable": {
            "type": "boolean"
          }
        }
      }
    }
  }
}


接下来,您可以使用nested查询来查询符合条件的数据,并使用nested_path参数来指定unitPriceStrategyList的路径。例如,查询memberId=157和memberRoleId=8的数据,可以使用以下查询:

GET /my_index/_search
{
  "query": {
    "nested": {
      "path": "unitPriceStrategyList",
      "query": {
        "bool": {
          "must": [
            {
              "term": {
                "unitPriceStrategyList.memberId": 157
              }
            },
            {
              "term": {
                "unitPriceStrategyList.memberRoleId": 8
              }
            }
          ]
        }
      }
    }
  }
}


接下来,您可以使用Elasticsearch的排序功能来对查询结果进行排序。例如,使用以下查询对数据按照memberPrice进行升序排序:

GET index_name/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "term": {
            "unitPriceStrategyList.commodityMemberList.memberId": {
              "value": 157
            }
          }
        },
        {
          "term": {
            "unitPriceStrategyList.commodityMemberList.memberRoleId": {
              "value": 8
            }
          }
        }
      ]
    }
  },
  "sort": [
    {
      "unitPriceStrategyList.memberPrice": {
        "order": "asc"
      }
    }
  ]
}


这个查询使用bool查询来限制memberId和memberRoleId,然后使用sort来按照unitPriceStrategyList.memberPrice进行升序排序。如果需要降序排序,只需要将"order"的值从"asc"改为"desc"即可。

参考GPT和自己的思路,可以使用Elasticsearch的function_score查询来实现这个需求,function_score查询可以通过将文档的_score与用户定义的函数结果相结合来重新计算文档的_score,从而影响排序。在这个需求中,我们可以使用function_score查询来重新计算文档的_score,以根据用户的角色和价格选择适当的价格进行排序。

以下是一个可能的查询示例,它使用function_score查询和nested聚合来实现上述需求:

{
  "query": {
    "function_score": {
      "query": {
        "match_all": {}
      },
      "functions": [
        {
          "filter": {
            "nested": {
              "path": "unitPriceStrategyList",
              "query": {
                "bool": {
                  "must": [
                    {
                      "term": {
                        "unitPriceStrategyList.isEnable": true
                      }
                    },
                    {
                      "nested": {
                        "path": "unitPriceStrategyList.commodityMemberList",
                        "query": {
                          "bool": {
                            "must": [
                              {
                                "term": {
                                  "unitPriceStrategyList.commodityMemberList.memberId": 157
                                }
                              },
                              {
                                "term": {
                                  "unitPriceStrategyList.commodityMemberList.memberRoleId": 8
                                }
                              }
                            ]
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "weight": 2
        },
        {
          "filter": {
            "nested": {
              "path": "unitPriceStrategyList",
              "query": {
                "bool": {
                  "must": [
                    {
                      "term": {
                        "unitPriceStrategyList.isEnable": true
                      }
                    },
                    {
                      "nested": {
                        "path": "unitPriceStrategyList.commodityMemberList",
                        "query": {
                          "bool": {
                            "must": [
                              {
                                "term": {
                                  "unitPriceStrategyList.commodityMemberList.memberId": 152
                                }
                              },
                              {
                                "term": {
                                  "unitPriceStrategyList.commodityMemberList.memberRoleId": 8
                                }
                              }
                            ]
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "weight": 1
        }
      ],
      "score_mode": "sum",
      "boost_mode": "multiply"
    }
  },
  "sort": [
    {
      "_score": {
        "order": "desc"
      }
    },
    {
      "price": {
        "order": "asc"
      }
    }
  ],
  "aggs": {
    "prices": {
      "nested": {
        "path": "unitPriceStrategyList"
      },
      "aggs": {
        "price": {
          "min": {
            "script": "doc['price'].value"
          }
        }
      }
    }
  }
}

该查询包括以下组件:

一个function_score查询,其中包括两个函数。第一个函数使用nested查询和bool查询来匹配适当的价格,并使用较高的权重来增加该价格的影响力。第二个函数使用相同的查询来匹配次佳价格,并使用较低的权重增加其影响力。两个函数的结果将相乘以影响排序顺序。

一个排序,首先按照匹配的价格降序排序,然后按照价格是否匹配以及价格值升序排序。这将使匹配的价格始终排在非匹配价格的前面,并按照价格值升序排序。

下面是一个可能的Elasticsearch查询示例,假设当前登录用户的memberId为157,memberRoleId为8:

{
  "query": {
    "function_score": {
      "query": {
        "bool": {
          "should": [
            {
              "nested": {
                "path": "unitPriceStrategyList",
                "score_mode": "sum",
                "query": {
                  "bool": {
                    "must": [
                      {
                        "range": {
                          "unitPriceStrategyList.memberPrice": {
                            "lte": 10000
                          }
                        }
                      },
                      {
                        "nested": {
                          "path": "unitPriceStrategyList.commodityMemberList",
                          "query": {
                            "bool": {
                              "must": [
                                {
                                  "terms": {
                                    "unitPriceStrategyList.commodityMemberList.memberId": [
                                      157
                                    ]
                                  }
                                },
                                {
                                  "terms": {
                                    "unitPriceStrategyList.commodityMemberList.memberRoleId": [
                                      8
                                    ]
                                  }
                                }
                              ]
                            }
                          }
                        }
                      }
                    ]
                  }
                },
                "boost_factor": 2
              }
            },
            {
              "nested": {
                "path": "unitPriceStrategyList",
                "score_mode": "sum",
                "query": {
                  "bool": {
                    "must": [
                      {
                        "range": {
                          "unitPriceStrategyList.memberPrice": {
                            "lte": 10000
                          }
                        }
                      },
                      {
                        "nested": {
                          "path": "unitPriceStrategyList.commodityMemberList",
                          "query": {
                            "bool": {
                              "must": [
                                {
                                  "terms": {
                                    "unitPriceStrategyList.commodityMemberList.memberId": [
                                      152
                                    ]
                                  }
                                },
                                {
                                  "terms": {
                                    "unitPriceStrategyList.commodityMemberList.memberRoleId": [
                                      8
                                    ]
                                  }
                                }
                              ]
                            }
                          }
                        }
                      }
                    ]
                  }
                },
                "boost_factor": 0.5
              }
            }
          ]
        }
      },
      "functions": [
        {
          "field_value_factor": {
            "field": "unitPriceStrategyList.memberPrice",
            "modifier": "log1p",
            "factor": 2
          }
        }
      ],
      "score_mode": "multiply"
    }
  },
  "sort": [
    {
      "_score": {
        "order": "desc"
      }
    },
    {
      "price": {
        "order": "asc"
      }
    }
  ]
}

在这个查询中,我们添加了一个嵌套查询,用于匹配次佳价格。我们使用相同的嵌套路径和布尔查询来匹配 memberRoleId=8 和 memberId=152 的价格。我们使用了一个较低的 boost_factor 来减小次佳价格的影响力。

最后,我们将两个函数的结果相乘以影响排序顺序,然后使用 _score 和价格字段进行排序。

希望这可以帮助你解决你的问题!

以下答案由GPT-3.5大模型与博主波罗歌共同编写:
可以通过Elasticsearch中的function_score query来实现这个需求,具体的步骤如下:

  1. 使用bool query把不在unitPriceStrategyList.commodityMemberList里面的文档和在里面但是会员价格为null的文档过滤掉。

  2. 使用function_score query对剩下的文档进行重打分操作。

  3. 在重打分操作中,使用script_score进行自定义脚本计算,根据当前登录人的memberId和memberRoleId计算出当前会员价格。

  4. 然后使用field_value_factor将计算出来的会员价格作为因子进行计算,达到根据会员价格进行排序的目的。

具体实现代码如下:

POST index/_search
{
  "query": {
    "bool": {
      "filter": [
        {
          "bool": {
            "should": [
              {
                "nested": {
                  "path": "unitPriceStrategyList",
                  "query": {
                    "bool": {
                      "must": [
                        {
                          "term": {
                            "unitPriceStrategyList.commodityMemberList.memberId": 157
                          }
                        },
                        {
                          "term": {
                            "unitPriceStrategyList.commodityMemberList.memberRoleId": 8
                          }
                        }
                      ]
                    }
                  }
                }
              },
              {
                "bool": {
                  "must_not": [
                    {
                      "nested": {
                        "path": "unitPriceStrategyList",
                        "query": {
                          "bool": {
                            "must": [
                              {
                                "exists": {
                                  "field": "unitPriceStrategyList.commodityMemberList.memberRoleId"
                                }
                              },
                              {
                                "term": {
                                  "unitPriceStrategyList.commodityMemberList.memberId": 157
                                }
                              }
                            ]
                          }
                        }
                      }
                    }
                  ]
                }
              }
            ]
          }
        }
      ]
    }
  },
  "sort": [
    {
      "_score": {
        "order": "desc"
      }
    },
    {
      "price": {
        "order": "desc"
      }
    }
  ],
  "size": 10,
  "from": 0, 
  "track_total_hits": true,
  "explain": true,
  "min_score": 0
  "rescore": {
    "window_size": 1000,
    "query": {
      "rescore_query": {
        "function_score": {
          "query": {"match_all": {}},
          "script_score": {
            "script": {
              "source": """
                def price = 0;
                boolean flag = false;
                for (def i = 0; i < doc['unitPriceStrategyList.commodityMemberList.memberId'].length; i++) {
                  if (doc['unitPriceStrategyList.commodityMemberList.memberId'][i].value == params.memberId &&
                      doc['unitPriceStrategyList.commodityMemberList.memberRoleId'][i].value == params.memberRoleId
                  ) {
                      if (doc['unitPriceStrategyList.memberPrice'].length > i && doc['unitPriceStrategyList.memberPrice'][i].value != null) {
                          price = doc['unitPriceStrategyList.memberPrice'][i].value;
                          flag = true;
                          break;
                      }
                  }
                }
                if (flag) {
                    return price;
                } else {
                    return doc['price'].value;
                }
              """,
              "params": {
                "memberId": 157,
                "memberRoleId": 8
              }
            }
          },
          "field_value_factor": {
            "field": "memberPrice",
            "factor": 1,
            "modifier": "log1p",
            "missing": 0
          },
          "boost_mode": "multiply"
        }
      }
    }
  }
}

这里使用了嵌套查询(nested query)来查询unitPriceStrategyList中的文档,然后使用function_score query来进行重打分操作,其中包含自定义脚本计算和field_value_factor计算因子。最后在sort中根据_score和price进行排序。
如果我的回答解决了您的问题,请采纳!

该回答引用ChatGPT

您可以使用Elasticsearch中的Function Score Query实现根据不同的会员进行排序。该查询可以通过定义一个或多个函数,以一种灵活的方式修改文档的分数,从而影响搜索结果的排序。

在这种情况下,您可以使用Script Score Function和Field Value Factor Function两个函数来实现您的需求。Script Score Function将通过比较当前登录人的ID和角色ID与unitPriceStrategyList中的commodityMemberList中的成员ID和角色ID来计算文档的得分。Field Value Factor Function将使用文档中的价格作为因子来调整得分。

以下是使用Function Score Query的示例查询:


{
  "query": {
    "function_score": {
      "query": {
        "match_all": {}
      },
      "functions": [
        {
          "script_score": {
            "script": {
              "source": """
              def member_id = params.member_id;
              def member_role_id = params.member_role_id;
              def unit_price_strategy_list = doc['unitPriceStrategyList'];
              
              def max_price = doc['price'].value;
              
              for (def i = 0; i < unit_price_strategy_list.length; i++) {
                def strategy = unit_price_strategy_list[i];
                
                for (def j = 0; j < strategy['commodityMemberList'].length; j++) {
                  def member = strategy['commodityMemberList'][j];
                  
                  if (member['memberId'] == member_id && member['memberRoleId'] == member_role_id) {
                    return strategy['memberPrice'];
                  }
                }
                
                if (strategy['isEnable'] && strategy['memberPrice'] > max_price) {
                  max_price = strategy['memberPrice'];
                }
              }
              
              return max_price;
              """,
              "params": {
                "member_id": 157,
                "member_role_id": 8
              }
            }
          }
        },
        {
          "field_value_factor": {
            "field": "price",
            "modifier": "log1p",
            "factor": 0.1
          }
        }
      ],
      "boost_mode": "multiply"
    }
  }
}

在这个查询中,我们使用了两个函数:Script Score Function和Field Value Factor Function。Script Score Function首先查找unitPriceStrategyList中与当前登录用户匹配的成员,并返回该成员的价格。如果没有匹配的成员,则返回最高价格。Field Value Factor Function将价格作为因子应用于得分,并使用log1p修改因子的影响,以避免价格因子对得分的影响过大。

请注意,此查询只是示例代码,并未经过测试和优化,因此可能需要根据您的具体需求进行调整和修改。