Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Modifying elasticsearch score based on nested field value

I want to modify scoring in ElasticSearch (v2+) based on the weight of a field in a nested object within an array.

For instance, using this data:

PUT index/test/0
{
    "name": "red bell pepper",
    "words": [
        {"text": "pepper", "weight": 20},
        {"text": "bell","weight": 10},
        {"text": "red","weight": 5}
    ]
}

PUT index/test/1
{
    "name": "hot red pepper",
    "words": [
        {"text": "pepper", "weight": 15},
        {"text": "hot","weight": 11},
        {"text": "red","weight": 5}
    ]
}

I want a query like {"words.text": "red pepper"} which would rank "red bell pepper" above "hot red pepper".

The way I am thinking about this problem is "first match the 'text' field, then modify scoring based on the 'weight' field". Unfortunately I don't know how to achieve this, if it's even possible, or if I have the right approach for something like this.

If proposing alternative approach, please try and keep a generalized idea where there are tons of different similar cases (eg: simply modifying the "red bell pepper" document score to be higher isn't really a suitable alternative).

like image 950
HotStuff68 Avatar asked Feb 11 '16 20:02

HotStuff68


People also ask

How do you query a nested field?

You can search nested fields using dot notation that includes the complete path, such as obj1.name . Multi-level nesting is automatically supported, and detected, resulting in an inner nested query to automatically match the relevant nesting level, rather than root, if it exists within another nested query.

What is nested field in Elasticsearch?

The nested type is a specialised version of the object data type that allows arrays of objects to be indexed in a way that they can be queried independently of each other.

What is score in Elasticsearch query?

In Elasticsearch, all document scores are positive 32-bit floating point numbers. If the script_score function produces a score with greater precision, it is converted to the nearest 32-bit float. Similarly, scores must be non-negative.


1 Answers

The approach you have in mind is feasible. It can be achieved via function score in a nested query .

An example implementation is shown below :

PUT test

PUT test/test/_mapping
{
   "properties": {
      "name": {
         "type": "string"
      },
      "words": {
         "type": "nested",
         "properties": {
            "text": {
               "type": "string"
            },
            "weight": {
               "type": "long"
            }
         }
      }
   }
}


PUT test/test/0
{
    "name": "red bell pepper",
    "words": [
        {"text": "pepper", "weight": 20},
        {"text": "bell","weight": 10},
        {"text": "red","weight": 5}
    ]
}
PUT test/test/1
{
    "name": "hot red pepper",
    "words": [
        {"text": "pepper", "weight": 15},
        {"text": "hot","weight": 11},
        {"text": "red","weight": 5}
    ]
}

post test/_search
{
   "query": {
      "bool": {
         "disable_coord": true,
         "must": [
            {
               "match": {
                  "name": "red pepper"
               }
            }
         ],
         "should": [
            {
               "nested": {
                  "path": "words",
                  "query": {
                     "function_score": {
                        "functions": [
                           {
                              "field_value_factor": {
                                "field" : "words.weight",
                                "missing": 0
                              }
                           }
                        ],
                        "query": {
                           "match": {
                              "words.text": "red pepper"
                           }
                        },
                        "score_mode": "sum",
                        "boost_mode": "replace"
                     }
                  },
                  "score_mode": "total"
               }
            }
         ]
      }
   }
}

Result :

 "hits": [
         {
            "_index": "test",
            "_type": "test",
            "_id": "0",
            "_score": 26.030865,
            "_source": {
               "name": "red bell pepper",
               "words": [
                  {
                     "text": "pepper",
                     "weight": 20
                  },
                  {
                     "text": "bell",
                     "weight": 10
                  },
                  {
                     "text": "red",
                     "weight": 5
                  }
               ]
            }
         },
         {
            "_index": "test",
            "_type": "test",
            "_id": "1",
            "_score": 21.030865,
            "_source": {
               "name": "hot red pepper",
               "words": [
                  {
                     "text": "pepper",
                     "weight": 15
                  },
                  {
                     "text": "hot",
                     "weight": 11
                  },
                  {
                     "text": "red",
                     "weight": 5
                  }
               ]
            }
         }
      ]
   }

The query in a nutshell would score a document that satisfies the must clause as follows : sum up the weights of the matched nested documents with the score of the must clause.

like image 135
keety Avatar answered Nov 14 '22 20:11

keety