Search code examples
elasticsearch

ElasticSearch custom script score that does not replace the natural scoring


I have an ElasticSearch query with a custom script score written in Painless language. For now the ES request looks like this, with the natural ES _score being totally replaced by a custom scoring from my script:

{
  "_source": {
    "excludes": [
      "field_to_exclude",
    ]
  },
  "from": 0,
  "size": 100,
  "query": {
    "script_score": {
      "query": {
        "bool": {
          "must": {
            "match_all": {}
          },
          "filter": [
            {
              "term": {
                "field_to_filter": 4
              }
            }
          ]
        }
      },
      "script": {
        "lang": "painless",
        "source": "COMPLEX_PAINLESS_SCRIPT"
      }
    }
  },
  "sort": [
    {
      "price": {
        "order": "asc"
      }
    },
    "_score"
  ]
}

Depending on some parameter from the frontend, I want to be able to still calculate the ES natural scoring separately, and keep this custom scoring to be calculated in another field, and even if possible being used as a secondary sorting criteria.

Is this possible?


Solution

  • I finally figured out. This can actually be done if you use a custom _script field in the sort array, along with the _score field for the natural ES score.

    And aside from that, we can keep the custom scoring in a specific field in the response by using script_fields.

    Both the ES requests, depending on the sorting criteria, would look like:

    {
      "_source": {
        "excludes": [
          "field_to_exclude",
        ]
      },
      "from": 0,
      "size": 100,
      "query": {
        "bool": {
          "must": {
            "match_all": {}
          },
          "filter": [
            {
              "term": {
                "field_to_filter": 4
              }
            }
          ]
        }
      },
      "sort": [
        {
          "price": {
            "order": "desc"
          }
        },
        {
          "_score": {
            "order": "desc"
          }
        },
        {
          "_script": {
            "type": "number",
            "script": {
              "lang": "painless",
              "source": "COMPLEX_PAINLESS_SCRIPT"
            },
            "order": "desc"
          }
        }
      ],
      "script_fields": {
        "custom_score": {
          "script": {
            "lang": "painless",
            "source": "COMPLEX_PAINLESS_SCRIPT"
          }
        }
      }
    }
    

    and

    {
      "_source": {
        "excludes": [
          "field_to_exclude",
        ]
      },
      "from": 0,
      "size": 100,
      "query": {
        "bool": {
          "must": {
            "match_all": {}
          },
          "filter": [
            {
              "term": {
                "field_to_filter": 4
              }
            }
          ]
        }
      },
      "sort": [
        {
          "_script": {
            "type": "number",
            "script": {
              "lang": "painless",
              "source": "COMPLEX_PAINLESS_SCRIPT"
            },
            "order": "desc"
          }
        },
        {
          "_score": {
            "order": "desc"
          }
        }
      ],
    
      "script_fields": {
        "fit_score": {
          "script": {
            "lang": "painless",
            "source": "COMPLEX_PAINLESS_SCRIPT"
          }
        }
      }
    }