Elasticsearch search document with nested document with optional fields - elasticsearch

I'm trying to create query for nested object that contains year and month. Both of them are optional. If some field not exists we treat them as hit. I found one solution but it causes combinatorial explosion of terms so I'm trying to find a better solution.
Steps of reproduction:
Creating index with mapping
PUT /date-test
{
"mappings": {
"properties": {
"datesOfBirth": {
"type": "nested"
}
}
}
}
Add documents with nested objects
PUT /date-test/_doc/1
{
"name": "Object1",
"datesOfBirth": []
}
PUT /date-test/_doc/2
{
"name": "Object2",
"datesOfBirth": [
{
"year": 1990,
"month": 4
}
]
}
PUT /date-test/_doc/3
{
"name": "Object3",
"datesOfBirth": [
{
"year": 1995,
"month": 2
},
{
"year": 1998,
"month": 4
}
]
}
PUT /date-test/_doc/4
{
"name": "Object4",
"datesOfBirth": [
{
"month": 4
}
]
}
This query works as expected for year range 1994-1996 and month range 1-5 (objects 1, 3, 4 are returned):
POST /date-test/_search
{
"size": 1000,
"query": {
"bool" : {
"should": [
{ "bool": {"must_not": [ //match when all fields are absent
{ "nested": { "path": "datesOfBirth", "query": { "exists": { "field": "datesOfBirth.year" }} }},
{ "nested": { "path": "datesOfBirth", "query": { "exists": { "field": "datesOfBirth.month" }} }}
]
}},
{ "bool": {"must_not": [ //match when year is absent but month exists and match to range
{ "nested": { "path": "datesOfBirth", "query": { "exists": { "field": "datesOfBirth.year" }} }}
],
"should": [
{"nested": { "path": "datesOfBirth", "query": { "bool": { "must": [
{ "range": { "datesOfBirth.month": { "gte": 1, "lte": 5} } }
]
}}}}
]
}},
{ "bool": {"must_not": [ //match when month is absent but year exists and match to range
{ "nested": { "path": "datesOfBirth", "query": { "exists": { "field": "datesOfBirth.month" }} }}
],
"should": [
{"nested": { "path": "datesOfBirth", "query": { "bool": { "must": [
{ "range": { "datesOfBirth.year": { "gte": 1994, "lte": 1996} } }
]
}}}}
]
}},
{"nested": { "path": "datesOfBirth", "query": { "bool": { "must": [ //both fields exists and must match to given ranges
{ "range": { "datesOfBirth.year": { "gte": 1994, "lte": 1996} } },
{ "range": { "datesOfBirth.month": { "gte": 1, "lte": 5} } }
]
}}}}
],
"minimum_should_match": 1
}
}
}
Is there better way to achieve that behaviour? I'm using Elasticsearch 7.1.

I've also tried always set field but with null in case of value absence and add mapping for year and month where I define null_value: -1. Then I can remove part with combination of field absence.
Create index with mapping
PUT /date-test
{
"mappings": {
"properties": {
"datesOfBirth": {
"type": "nested",
"properties": {
"year": { "type": "integer", "null_value": -1 },
"month": { "type": "integer", "null_value": -1 }
}
}
}
}
}
Creating documents as follows:
PUT /date-test/_doc/7
{
"name": "SomeObjectWithoutYear",
"datesOfBirth": [
{
"year": null,
"month": 4
}
]
}
Then I can do query like this:
POST /date-test/_search
{
"size": 1000,
"query": {
"bool" : {
"should": [
{ "bool": {"must_not": [
{ "nested": { "path": "datesOfBirth", "query": { "exists": { "field": "datesOfBirth.year" }} }},
{ "nested": { "path": "datesOfBirth", "query": { "exists": { "field": "datesOfBirth.month" }} }}
]
}},
{"nested": { "path": "datesOfBirth", "query": { "bool": { "should": [
{ "match": { "datesOfBirth.year": { "query": -1 } } },
{ "match": { "datesOfBirth.month": { "query": -1 } } },
{ "range": { "datesOfBirth.year": { "gte": 1994, "lte": 1996} } },
{ "range": { "datesOfBirth.month": { "gte": 1, "lte": 5} } }
],
"minimum_should_match": 2
}}}}
],
"minimum_should_match": 1
}
}
}
But I'm wondering if it is the cleanest way to achieve that.

Related

Minimum should match with filter doesn't return any result

I have a complicated query which works fine.the proble is that I'm going to add a condition(filter) to it to filter the result.I need the exact result that I currently get with filtering based on the field called "field7".
"query": {
"bool": {
"should": [
{
"match_bool_prefix": {
"field1": {
"query": "test",
"fuzziness": "auto",
"boost": 1
}
}
},
{
"match": {
"field2": {
"query": "test",
"boost": 10
}
}
},
{
"exists": {
"field": "field3",
"boost": 15
}
},
{
"exists": {
"field": "field4",
"boost": 10
}
},
{
"match_phrase_prefix": {
"field5": {
"query": ""
}
}
}
],
"must": [
{
"bool": {
"filter": [
{
"match": {
"field6": "A"
}
},
{"terms": { "field7": [3,4,5]}}
]
}
}
],
"minimum_should_match": 3
}
},
"size": 20
I have to use "minimum_should_match": 3,to meet my requirements(If i remove it I get unrelated results) but when i use it with filter the result gets notthing.Is there any suggestion how to get current result and filter it based on field7?
#Paris I believe you can use filter term query for field7 since you want to apply filter on the result-set from should+must query. So basically this should suffice:
"query": {
"bool": {
"should": [
{
"match_bool_prefix": {
"field1": {
"query": "test",
"fuzziness": "auto",
"boost": 1
}
}
},
{
"match": {
"field2": {
"query": "test",
"boost": 10
}
}
},
{
"exists": {
"field": "field3",
"boost": 15
}
},
{
"exists": {
"field": "field4",
"boost": 10
}
},
{
"match_phrase_prefix": {
"field5": {
"query": ""
}
}
}
],
"must": {
{"match": {"field6": "A"}}
},
"filter": {
{"term" : {"field7" : 3}},
{"term" : {"field7" : 4}},
{"term" : {"field7" : 5}},
}
}
},
"size": 20

Custom sort after score calculation and the value of specific field

Need help to understand sorting in a query where _score is either 0 or 1 based on some conditions
but there is also an additional_filed type integer, sort should be in the order of _score * additional_field
sample index
PUT /product_t
{
"mappings": {
"properties": {
"name": {
"type": "text"
},
"placed" :{
"type": "integer"
},
"store" : {
"type": "nested"
}
}
}
}
sample document
PUT /product_t/_doc/10
{
"name": "awesome",
"count_sold": 199,
"stock": [
{
"id": 1,
"count": 10
},
{
"id": 2,
"count": 5
},
{
"id": 3,
"count": 0
}
]
}
Query Used :
POST /product_t/_search
{
"from": 0,
"size": 100,
"timeout": "300ms",
"query": {
"bool": {
"filter": [
{
"match": {
"name": {
"value": "awesome"
}
}
},
{
"nested": {
"path": "stock",
"query": {
"bool": {
"must": [
{
"match": {
"stock.id": 3
}
}
]
}
}
}
}
],
"should": [
{
"constant_score": {
"filter": {
"nested": {
"path": "stock",
"query": {
"bool": {
"must": [
{
"match": {
"stock.id": 3
}
},
{
"range": {
"stock.count": {
"gt": 0
}
}
}
]
}
}
}
},
"boost": 1
}
},
{
"constant_score": {
"filter": {
"nested": {
"path": "stock",
"query": {
"bool": {
"must": [
{
"match": {
"stock.id": 3
}
},
{
"range": {
"stock.count": {
"lte": 0
}
}
}
]
}
}
}
},
"boost": 0
}
}
]
}
}
}
count_sold is additional field
You can use script sort
"sort": {
"_script": {
"type": "number",
"script": {
"lang": "painless",
"source": "doc['count_sold'].value * _score"
},
"order": "asc"
}
}

Elasticsearch - boosting specific documents in every search

I'm very new to Elasticsearch. I'm using it to filtering and also boosting some fields at query time. This is the code part for boosting and filtering:
"query": {
"bool": {
"must": [
{
"bool": {
"should": [
{
"multi_match": {
"type": "best_fields",
"query": "exampleKeyword",
"fields": [
"exampleField1^0",
"exampleField2^50",
"exampleField3^10",
"exampleField4^10",
"exampleField5^5"
],
"boost": 50
}
}]
}
}
],
"filter": [
{
"bool": {
"must": [
{
"bool": {
"must": [
{
"term": {
"bla": {
"value": ""
}
}
}
]
}
}, {
"term": {
"active": {
"value": "true"
}
}
},
{
"range": {
"closingDate": {
"gte": "201710310000",
"lte": "999912312359"
}
}
},
Now I want to boost some specific documents. I'll give an array of integers for example Field6 and if my search results contain the elements of the array, these documents should get boosted with, I dont know, 100 to my scale.
How can I do this? Finally I dont want to expand the result set. Just want to boost more the desired ids if results contain these ids.
Using function_score you can do something around these lines:
{
"query": {
"bool": {
"must": [
{
"function_score": {
"query": {
"bool": {
"should": [
{
"multi_match": {
"type": "best_fields",
"query": "bla",
"fields": [
"exampleField1^0",
"exampleField2^50",
"exampleField3^10",
"exampleField4^10",
"exampleField5^5"
],
"boost": 50
}
}
]
}
},
"functions": [
{
"filter": {
"ids": {
"values": [
1,
5
]
}
},
"weight": 10
}
],
"score_mode": "max",
"boost_mode": "multiply"
}
}
],
"filter": [
{
"bool": {
"must": [
{
"bool": {
"must": [
{
"term": {
"bla": {
"value": ""
}
}
}
]
}
},
{
"term": {
"active": {
"value": "true"
}
}
},
{
"range": {
"closingDate": {
"gte": "201710310000",
"lte": "999912312359"
}
}
}
]
}
}
]
}
}
}

Elastic Search Nested Query with Nested Object

This is the type of data I have stored on my index in elastic search.
I have to find Recipes with Main Ingredient Beef(and weight less than 1000) with Ingredients -(chilli powder and weight less than 250),(olive oil & weight less than 300 )and similarly for all other ingredients.
"Name": "Real beef burritos",
"Ingredients": [
{"name": "olive oil",
"id": 27,
"weight": 200},
{"name": "bonion","id": 3,"weight": 300},
{"name": "garlic",
"id": 2,
"weight": 100
},
{"name": "chilli powder",
"id": 35,
"weight": 150},
{"name": "coriander",
"id": 40,
"weight": 600},
{"name": "tortillas",
"id": 41,
"weight": 700}
],"Main_ingredient": {
"type": "Beef",
"id": 101,
"weight": 1000
}}}
Mapping of the index is
{"final":{"mappings":{"superb":{"properties":{"Cook Time":{"type":"long"},"Ingredients":{"type":"nested","properties":{"id":{"type":"short"},"name":{"type":"string"},"type":{"type":"string"},"weight":{"type":"short"}}},"Main_ingredient":{"properties":{"id":{"type":"long"},"type":{"type":"string"},"weight":{"type":"long"}}},"Name":{"type":"string"},"Prep Time":{"type":"long"},"Servings":{"type":"long"},"Tags":{"type":"string"},"Urls":{"type":"string"},"Views":{"type":"long"}}}}}}
My query is
{
"query": {
"bool": {
"must": [
{ "match": { "Main_ingredient.type": "Beef" }},
{"range":{"Main_ingredient.weight":{"lte":1000}}},
{
"nested": {
"path": "Ingredients",
"query": {
"bool": {
"must": [
{ "match": { "Ingredients.name": "garlic" }},
{ "range": { "Ingredients.weight":{"lte":400} }},
{ "match": { "Ingredients.name": "chilli powder" }},
{ "range": { "Ingredients.weight":{"lte":400} }}
]
}}}}
]
}}}
But it gives Null.Can anyone help me out .I think I am not using nested query properly
Try this:
{
"query": {
"bool": {
"must": [
{
"match": {
"Main_ingredient.type": "Beef"
}
},
{
"range": {
"Main_ingredient.weight": {
"lte": 1000
}
}
},
{
"nested": {
"path": "Ingredients",
"query": {
"bool": {
"must": [
{
"match": {
"Ingredients.name": "garlic"
}
},
{
"range": {
"Ingredients.weight": {
"lte": 400
}
}
}
]
}
}
}
},
{
"nested": {
"path": "Ingredients",
"query": {
"bool": {
"must": [
{
"match": {
"Ingredients.name": "chilli powder"
}
},
{
"range": {
"Ingredients.weight": {
"lte": 400
}
}
}
]
}
}
}
}
]
}
}
}

Using Elasticsearch, how do I do a filtered query on both my document properties and nested document properties?

Here is an example document source.
{
"tags": [
"meow",
"cats",
"feline"
],
"visible": 1,
"for_sale": "y",
"title": "Cat Meow",
"stock": [{
"department": "mens",
"size": "small"
}, {
"department": "mens",
"size": "medium"
}]
}
I want to find documents that are 'stock.department=mens' and 'stock.size=medium' and also are 'for_sale=y'
Here is the query that I've come up with so far. I can't figure out how to filter by for_sale=y.
{
"size": 5,
"query": {
"filtered": {
"query": {
"multi_match": {
"fields": ["title", "tags"],
"query": "cat"
}
},
"filter": {
"nested": {
"path": "stock",
"filter": {
"bool": {
"must": [{
"term": {
"stock.size": "medium"
}
}, {
"term": {
"stock.department": "mens"
}
}]
}
}
}
}
}
}
}
This is what I've come up with. If anyone has any critiques or improvements please share them.
{
"size": 5,
"query": {
"filtered": {
"query": {
"multi_match": {
"fields": ["title", "tags"],
"query": "cat"
}
},
"filter": {
"bool": {
"must": [{
"term": {
"for_sale": "y"
}
}, {
"term": {
"visible": 1
}
}, {
"nested": {
"path": "stock",
"filter": {
"bool": {
"must": [{
"term": {
"stock.size": "medium"
}
}, {
"term": {
"stock.department": "mens"
}
}]
}
}
}
}]
}
}
}
}
}

Resources