Search code examples
javascriptelasticsearch

Elasticsearch sorting by custom item weight


I have stored the documents which include status property. I would like to sort the documents by status priority (not status alphabetically). I have followed previous answers and composed the following function which still doesnt work as expected; the documents are sorted by status names (alphabetically):

function getESSortingByStatusQuery(query, order) {
        let statusOrder = ['BLUE', 'RED', 'BLACK', 'YELLOW', 'GREEN'];
        if(order == 'desc'){
            statusOrder.reverse();
        }
        const functions = statusOrder.map((item) => {
            const idx = statusOrder.indexOf(item);
            return {filter: {match: {statusColor: item}},
                weight: (idx + 1) * 50}
        });
        const queryModified = {
            "function_score": {
                "query": {"match_all": {}}, // this is for testing purposes and should be replaced with original query
                "boost": "5",
                "functions": functions,
                "score_mode": "multiply",
                "boost_mode": "replace"
            }
        }
        return queryModified;
    }

I would be thankful if anyone suggested the way to sort items according to predefined priority of the property (in this case status).


Solution

  • Below is a sample custom sort script which I think is what you are looking for. I've added sample mapping, documents, query and the response as how it appears.

    Mapping:

    PUT color_index
    {
      "mappings": {
        "properties": {
          "color":{
            "type": "keyword"
          },
          "product":{
            "type": "text"
          }
        }
      }
    }
    

    Sample Documents:

    POST color_index/_doc/1
    {
      "color": "BLUE",
      "product": "adidas and nike"
    }
    
    POST color_index/_doc/2
    {
      "color": "GREEN",
      "product": "adidas and nike and puma"
    }
    
    POST color_index/_doc/3
    {
      "color": "GREEN",
      "product": "adidas and nike"
    }
    
    POST color_index/_doc/4
    {
      "color": "RED",
      "product": "nike"
    }
    
    POST color_index/_doc/5
    {
      "color": "RED",
      "product": "adidas and nike"
    }
    

    Query:

    POST color_index/_search
    {
      "query": {
        "bool": {
          "must": [
            {
              "query_string": {
                "default_field": "*",
                "query": "adidas OR nike"
              }
            }
          ]
        }
      },
      "sort": [
        { "_score": { "order": "desc"} },          <---- First sort by score
        { "_script": {                             <---- Second sort by Colors
                "type": "number",
                "script": {
                    "lang": "painless",
                    "source": "if(params.scores.containsKey(doc['color'].value)) { return params.scores[doc['color'].value];} return 100000;",
                    "params": {
                        "scores": {
                            "BLUE": 0,
                            "RED": 1,
                            "BLACK": 2,
                            "YELLOW": 3,
                            "GREEN": 4
                        }
                    }
                },
                "order": "asc"
            }
    
        }
      ]
    }
    

    Firstly it would return documents sorted by its score, and then it would apply the second sorting logic to that result.

    For the second sorting, i.e. using script sort, notice how I have added the numeric values to the colors in the scores section. You would need to construct your query accordingly.

    The logic as how it works is in the source section which I believe is self-explainable, where I used doc['color'].value as that was my field on which I'm applying custom sort logic.

    Response:

    {
      "took" : 5,
      "timed_out" : false,
      "_shards" : {
        "total" : 1,
        "successful" : 1,
        "skipped" : 0,
        "failed" : 0
      },
      "hits" : {
        "total" : {
          "value" : 5,
          "relation" : "eq"
        },
        "max_score" : null,
        "hits" : [
          {
            "_index" : "color_index",
            "_type" : "_doc",
            "_id" : "1",
            "_score" : 0.5159407,
            "_source" : {
              "color" : "BLUE",
              "product" : "adidas and nike"
            },
            "sort" : [
              0.5159407,                     <--- This value is score(desc by nature)
              0.0                            <--- This value comes from script sort as its BLUE and I've used value 0 in the script which is in 'asc' order
            ]
          },
          {
            "_index" : "color_index",
            "_type" : "_doc",
            "_id" : "5",
            "_score" : 0.5159407,
            "_source" : {
              "color" : "RED",
              "product" : "adidas and nike"
            },
            "sort" : [
              0.5159407,
              1.0
            ]
          },
          {
            "_index" : "color_index",
            "_type" : "_doc",
            "_id" : "3",
            "_score" : 0.5159407,
            "_source" : {
              "color" : "GREEN",
              "product" : "adidas and nike"
            },
            "sort" : [
              0.5159407,
              4.0
            ]
          },
          {
            "_index" : "color_index",
            "_type" : "_doc",
            "_id" : "2",
            "_score" : 0.40538198,
            "_source" : {
              "color" : "GREEN",
              "product" : "adidas and nike and puma"
            },
            "sort" : [
              0.40538198,
              4.0
            ]
          },
          {
            "_index" : "color_index",
            "_type" : "_doc",
            "_id" : "4",
            "_score" : 0.10189847,
            "_source" : {
              "color" : "RED",
              "product" : "nike"
            },
            "sort" : [
              0.10189847,
              1.0
            ]
          }
        ]
      }
    }
    

    Notice the first three documents, it has exact value of product but different color and you can see that they are grouped together as we first sorted by _score then we sort that by color

    Let me know if this helps!