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 :)

mappy
  • 367
  • 5
  • 19

1 Answers1

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
                    }
                  }
                ]
              }
            }
          }
        }
      ]
    }
  }
}
jaspreet chahal
  • 8,817
  • 2
  • 11
  • 29
  • 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? – mappy May 23 '20 at 07:05
  • Oh, of course! After resolve this issue, I will definitely adopt the answer :) – mappy May 23 '20 at 07:11
  • @mappy for nested fields, nested query needs to be used. I have updated answer – jaspreet chahal May 23 '20 at 07:43
  • 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 May 23 '20 at 08:05
  • @mappy You can use multi match for nested and a separate multimatch for non nested. You cannot combine them together. You need to replace match with multi match in query – jaspreet chahal May 23 '20 at 08:06
  • Thank you ! I added multi match to your query, and tested it. Overall it work well. But the final score is not created only by the function, but is affected by the query results of the multi match. Is there any way to determine the score only with menu.count? – mappy May 23 '20 at 08:27
  • wow... It's impressive that you use Elasticsearch's features properly for your purposes. Really appreciated for your help!! – mappy May 23 '20 at 21:26
  • 1
    @mappy glad could be of help – jaspreet chahal May 24 '20 at 01:34