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