2

I'm using Elasticsearch 7.6

I have documents in Restaurant index which look like this:

  "name" : "ABC restaurant",
  "menu" : [
    {
      "name" : "chicken",
      "count" : 23
    },
    {
      "name" : "rice",
      "count" : 10        }
   ]

Count means the number of orders received.

When a customer searches by menu name in the website, I would like to give a high score to a restaurant with a high count of the menu among several restaurants and expose it to the top of the search results.

To do this, it seems to be necessary to know the matched menu in each document in the painless script.

I'm wondering it is possible. And if so, how can I do it?


UPDATED Thanks for your answer @jaspreet chahal

I made index like this:

PUT restaurant
{
  "mappings": {
    "properties": {
      "name": {
        "type": "text"
      },
      "menu":{
        "type": "nested", 
        "properties": {
          "name": {"type": "text"},
          "count": {"type": "integer"}
        }
      }
    }
  }
}

POST /restaurant/_doc/1
{
  "name": "ABC Restaurant",
  "menu": [
    {"name": "chicken", "count": 3},
    {"name": "cake", "count": 5}
  ]
}

POST /restaurant/_doc/2
{
  "name": "TEST Restaurant",
  "menu": [
    {"name": "chicken", "count": 10},
    {"name": "cake", "count": 7},
    {"name": "rice", "count": 2}
  ]
}


POST /restaurant/_doc/3
{
  "name": "Good Restaurant",
  "menu": [
    {"name": "chicken", "count": 20},
    {"name": "cake", "count": 13},
    {"name": "rice", "count": 5}
  ]
}

What I'm trying to do is to get total score based on matched menu count while using multi match, like this:

GET restaurant/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "function_score": {
            "query": {
              "bool": {
                "must": [
                  {
                    "multi_match": {
                      "query": "chicken",
                      "type": "cross_fields",
                      "fields": [
                        "menu.name", 
                        "name"
                      ],
                      "operator": "and"
                    }
                  }
                ]
              }
            },
            "boost_mode": "replace",
            "functions": [
              {
                "field_value_factor": {
                  "field": "menu.count",
                  "missing": 0
                }
              }
            ]
          }
        }
      ]
    }
  }
}

But the query above doens't get any result.

To make it work, I added 'include_in_root:True' to menu mapping. But in this case, I can't get proper score.. (It seems that the lowest score of the menu count was obtained regardless of the search word)

May I ask how to make this work as I expect? Thanks !


UPDATE Again.

I added multi match to your query

GET restaurant/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "multi_match": {
            "query": "Good Restaurant chicken", 
            "type": "cross_fields", 
            "fields": [
              "menu.name",
              "name"
            ]
          }
        },
        {
          "nested": {
            "path": "menu",
            "query": {
              "function_score": {
                "query": {
                  "bool": {
                    "should": [
                      {
                        "match": {
                          "menu.name": {
                            "query": "Good Restaurant chicken",
                            "operator": "or"
                          }
                        }
                      }
                    ]
                  }
                },
                "boost_mode": "replace",
                "functions": [
                  {
                    "field_value_factor": {
                      "field": "menu.count",
                      "missing": 0
                    }
                  }
                ]
              }
            }
          }
        }
      ]
    }
  }
}

It get all results well! But the score was affected by multi match query.

This is result of query:

  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 3,
      "relation" : "eq"
    },
    "max_score" : 21.11436,
    "hits" : [
      {
        "_index" : "restaurant",
        "_type" : "_doc",
        "_id" : "3",
        "_score" : 21.11436,
        "_source" : {
          "name" : "Good Restaurant",
          "menu" : [
            {
              "name" : "chicken",
              "count" : 20
            },
            {
              "name" : "cake",
              "count" : 13
            },
            {
              "name" : "rice",
              "count" : 5
            }
          ]
        }
      },
      {
        "_index" : "restaurant",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 10.133532,
        "_source" : {
          "name" : "TEST Restaurant",
          "menu" : [
            {
              "name" : "chicken",
              "count" : 10
            },
            {
              "name" : "cake",
              "count" : 7
            },
            {
              "name" : "rice",
              "count" : 2
            }
          ]
        }
      },
      {
        "_index" : "restaurant",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 3.1335313,
        "_source" : {
          "name" : "ABC Restaurant",
          "menu" : [
            {
              "name" : "chicken",
              "count" : 3
            },
            {
              "name" : "cake",
              "count" : 5
            }
          ]
        }
      }
    ]
  }
}

Thank you very much for your answer :)

1 Answer 1

1

You can use function_score to give higher score to nested documents based on count value.

Query:

{
  "query": {
    "nested": {
      "path": "menu",
      "query": {
        "function_score": {
          "score_mode": "sum",
          "boost_mode": "replace",
          "query": {
            "match": {
              "menu.name": "chicken"
            }
          },
          "functions": [
            {
              "field_value_factor": {
                "field": "menu.count"
              }
            }
          ]
        }
      }
    }
  }
}

Result:

"hits" : [
      {
        "_index" : "index63",
        "_type" : "_doc",
        "_id" : "tA8IPHIBzLrvZDnz-ghE",
        "_score" : 23.0,
        "_source" : {
          "name" : "ABC restaurant",
          "menu" : [
            {
              "name" : "chicken",
              "count" : 23
            },
            {
              "name" : "rice",
              "count" : 10
            }
          ]
        }
      },
      {
        "_index" : "index63",
        "_type" : "_doc",
        "_id" : "tQ8JPHIBzLrvZDnz-AiA",
        "_score" : 20.0,
        "_source" : {
          "name" : "XYZ restaurant",
          "menu" : [
            {
              "name" : "chicken",
              "count" : 20
            },
            {
              "name" : "rice",
              "count" : 8
            }
          ]
        }
      }
    ]

Edit1: For nested fields you need to use nested query, you cannot run search on these fields directly.

{
  "query": {
    "bool": {
      "should": [
        {
          "match": {
            "name": {
              "operator": "and",
              "query": "chicken"
            }
          }
        },
        {
          "nested": {
            "path": "menu",
            "query": {
              "function_score": {
                "query": {
                  "bool": {
                    "must": [
                      {
                        "match": {
                          "menu.name": {
                            "query": "chicken",
                            "operator": "and"
                          }
                        }
                      }
                    ]
                  }
                },
                "boost_mode": "replace",
                "functions": [
                  {
                    "field_value_factor": {
                      "field": "menu.count",
                      "missing": 0
                    }
                  }
                ]
              }
            }
          }
        }
      ]
    }
  }
}

Edit2: To consider score only from nested query , you can either give it higher boost so that documents matching your nested score are scored higher. If you don't want your multi-match to have any score. You can place it in constant_score with 0 boost, documents matching this will have 0 score

{
  "query": {
    "bool": {
      "should": [
        {
          "constant_score": {
            "filter": {
              "multi_match": {
                "query": "Good Restaurant chicken",
                "type": "cross_fields",
                "fields": [
                  "name"
                ]
              }
            },
            "boost": 0
          }
        },
        {
          "nested": {
            "path": "menu",
            "query": {
              "function_score": {
                "query": {
                  "bool": {
                    "should": [
                      {
                        "match": {
                          "menu.name": {
                            "query": "Good Restaurant chicken",
                            "operator": "or"
                          }
                        }
                      }
                    ]
                  }
                },
                "boost_mode": "replace",
                "functions": [
                  {
                    "field_value_factor": {
                      "field": "menu.count",
                      "missing": 0
                    }
                  }
                ]
              }
            }
          }
        }
      ]
    }
  }
}
Sign up to request clarification or add additional context in comments.

8 Comments

Hi jaspreet chahal. Your answer really helped me a lot! I get to know more about 'Nested' mapping. I tried things based on what you told me. But I couldn't make it work well yet. I updated this question, can you take a look?
Oh, of course! After resolve this issue, I will definitely adopt the answer :) 🙏
@mappy for nested fields, nested query needs to be used. I have updated answer
Thanks for your quick reply! In fact, there are multiple fields, so I must use multi match. Sorry for the additional questions..! How can I use multi match in your answer ??
@mappy glad could be of help
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.