Elasticsearch scripting array size - elasticsearch

Can anyone help me to construct below query. I get below error, when running this query. ES version is 7.9.0;
In my model there is a field "repliedBy" which is an array field. It's value is always initialized with empty array. But on some entities it has one or couple of objects. I need to write a query to get all items with empty array only.
GET myTable/_search
{
"query": {
"bool": {
"must": [
{
"script": {
"script": {
"source": "doc['repliedBy'].size() == params.val",
"params": {
"val": 0
}
}
}
},
{
"range": {
"receivedDate": {
"gte": "2020-09-15T07:51:21.000Z",
"lte": "2020-12-01T07:51:21.000Z"
}
}
}
]
}
}
}
Error:
"error" : {
"root_cause" : [
{
"type" : "script_exception",
"reason" : "runtime error",
"script_stack" : [
"org.elasticsearch.search.lookup.LeafDocLookup.get(LeafDocLookup.java:90)",
"org.elasticsearch.search.lookup.LeafDocLookup.get(LeafDocLookup.java:41)",
"doc['repliedBy'].size() == params.val",
" ^---- HERE"
],
"script" : "doc['repliedBy'].size() == params.val",
"lang" : "painless",
"position" : {
"offset" : 4,
"start" : 0,
"end" : 37
}
}
],

This is the job for a bool/must_not/exists query combination, like this:
{
"query": {
"bool": {
"must_not": [
{
"exists": {
"field": "repliedBy.id"
}
}
],
"filter": [
{
"range": {
"receivedDate": {
"gte": "2020-09-15T07:51:21.000Z",
"lte": "2020-12-01T07:51:21.000Z"
}
}
}
]
}
}
}

Related

Elastic search combine must and must_not

I have a document that holds data for a product the mapping is as follow:
"mappings" : {
"properties" : {
"view_score" : {
"positive_score_impact" : true,
"type" : "rank_feature"
},
"recipients" : {
"dynamic" : false,
"type" : "nested",
"enabled" : true,
"properties" : {
"type" : {
"similarity" : "boolean",
"type" : "keyword"
},
"title" : {
"type" : "text",
"fields" : {
"key" : {
"type" : "keyword"
}
}
}
}
}
}
}
And I have 2 documents with the following data:
{
"view_score": 10,
"recipients": [{"type":"gender", "title":"male"}, {"type":"gender", "title":"female"}]
}
{
"view_score": 10,
"recipients": [{"type":"gender", "title":"female"}]
}
When a user searches for a product she can say "I prefer products for females" so The products which specifies gender as just female should come before products that specifies gender as male and female both.
I have the following query which gives more score to products with just female gender:
GET _search
{
"sort": [
"_score"
],
"query": {
"script_score": {
"query": {
"bool": {
"should": [
{
"nested": {
"path": "recipients",
"ignore_unmapped": true,
"query": {
"bool": {
"boost": 10,
"must": [
{
"term": {
"recipients.type": "gender"
}
},
{
"match": {
"recipients.title": "female"
}
}
],
"must_not": {
"bool": {
"filter": [
{
"term": {
"recipients.type": "gender"
}
},
{
"match": {
"recipients.title": "male"
}
}
]
}
}
}
}
}
}
]
}
},
"script": {
"source": "return _score;"
}
}
}
}
But if I add another query to should query it won't behave the same and gives the same score to products with one or two genders in their specifications.
here is my final query which wont work as expected:
GET _search
{
"sort": [
"_score"
],
"query": {
"script_score": {
"query": {
"bool": {
"should": [
{
"rank_feature": {
"field": "view_score",
"linear": {}
}
},
{
"nested": {
"path": "recipients",
"ignore_unmapped": true,
"query": {
"bool": {
"boost": 10,
"must": [
{
"term": {
"recipients.type": "gender"
}
},
{
"match": {
"recipients.title": "female"
}
}
],
"must_not": {
"bool": {
"filter": [
{
"term": {
"recipients.type": "gender"
}
},
{
"match": {
"recipients.title": "male"
}
}
]
}
}
}
}
}
}
]
}
},
"script": {
"source": "return _score;"
}
}
}
}
So my problem is how to combine these should clause together to give more weight to the products that specify only one gender.

Elasticsearch, can't remove a field inside nested field

I have mappings
{
"candidate-index" : {
"mappings" : {
"properties" : {
"provider_candidates" : {
"type" : "nested",
"properties" : {
"foo" : {
"type" : "object"
},
"group_key" : {
"type" : "keyword"
}
}
}
}
}
}
I want to delete foo field
POST /candidate-index/_update_by_query
{
"script" : "ctx._source.remove(\"provider_candidates.foo\")",
"query": {
"nested": {
"path": "provider_candidates",
"query": {
"bool": {
"must": [
{
"exists": {
"field": "provider_candidates.foo"
}
}
]
}
}
}
}
}
It doesn't work. It doesn't generate an error, but the field is not removed.
I know the query part is correct, because if I turn it into _search it correctly finds documents
I also tried
POST /candidate-index/_update_by_query
{
"script" : "ctx._source.provider_candidates.remove(\"foo\")",
"query": {
"nested": {
"path": "provider_candidates",
"query": {
"bool": {
"must": [
{
"exists": {
"field": "provider_candidates.foo"
}
}
]
}
}
}
}
}
it says
{
"error" : {
"root_cause" : [
{
"type" : "script_exception",
"reason" : "runtime error",
"script_stack" : [
"ctx._source.provider_candidates.remove(\"foo\")",
" ^---- HERE"
],
"script" : "ctx._source.provider_candidates.remove(\"foo\")",
"lang" : "painless"
}
],
"type" : "script_exception",
"reason" : "runtime error",
"script_stack" : [
"ctx._source.provider_candidates.remove(\"foo\")",
" ^---- HERE"
],
"script" : "ctx._source.provider_candidates.remove(\"foo\")",
"lang" : "painless",
"caused_by" : {
"type" : "wrong_method_type_exception",
"reason" : "cannot convert MethodHandle(List,int)Object to (Object,String)Object"
}
},
"status" : 400
}
You need to loop provider_candidates field and then delete field inside it
POST /index51/_update_by_query
{
"script" : "for (int i = 0; i < ctx._source.provider_candidates.length; ++i) { ctx._source.provider_candidates[i].remove(\"foo\") }",
"query": {
"nested": {
"path": "provider_candidates",
"query": {
"bool": {
"must": [
{
"exists": {
"field": "provider_candidates.foo"
}
}
]
}
}
}
}
}

Elasticsearch - Search and filter by price

I have some array with promocodes (comes from request):
$promocodes = ['K1H5E1F1', 'M4C8A5K6', 'A3B9A45KL'];
And I have products data in Elasticsearch (as example, I will give data of one product):
// First product (2 promocodes matched, take a lower price 265.5 and filter this product at this price)
"price": 199,
"promocodes" : [
{
"code" : "K1H5E1F1",
"price" : 265.5
},
{
"code" : "LKDS3534K",
"price" : 357
},
{
"code" : "A3B9A45KL",
"price" : 327.5
}
]
// Second product (1 promocode matched, take a price 700 and filter this product at this price)
"price": 800,
"promocodes" : [
{
"code" : "AJ543HJB",
"price" : 500
},
{
"code" : "M4C8A5K6",
"price" : 700
}
]
// Third product (0 promocode matched, take a base price 900 and filter this product at this price)
"price": 900,
"promocodes" : [
{
"code" : "AJ87HJ90",
"price" : 750
}
]
I need to filter products data by price based on promocodes. If you set a range for the price and have promocodes, then you need to filter the products. If the product has the same promocode, then you need to take the price for this promotional code, not the main price. If 2 promocodes match for one product, then you need to take a lower price. In my example, the same product has 2 promotional codes for one product, I need to take the lower price out of 2 prices for the promocode and filter for that particular price.
This request does not filter prices as I need:
GET dev_products/_search
{
"query": {
"bool": {
"must": [
{
"range": {
"price": {
"gte": 100,
"lte": 350
}
}
},
{
"nested": {
"path": "promocodes",
"query": {
"terms": {
"promocodes.code": [
'K1H5E1F1',
'M4C8A5K6',
'A3B9A45KL'
]
}
}
}
}
]
}
}
}
I don't know how to make a request correctly, I ask you for help.
You need to use inner hits.
{
"query": {
"bool": {
"must": [
{
"range": {
"price": {
"gte": 100,
"lte": 350
}
}
},
{
"nested": {
"path": "promocodes",
"query": {
"terms": {
"promocodes.code": [
"K1H5E1F1",
"A3B9A45KL"
]
}
},
"inner_hits": {
"sort": {"promocodes.price": "asc"},----> sort nested document by price
"size": 1 ---> return top 1 document
}
}
}
]
}
}
}
Result:
"hits" : [
{
"_index" : "index4",
"_type" : "_doc",
"_id" : "NTBFgm0BFLPFo7KPt70j",
"_score" : 2.0,
"_source" : {
"price" : 199,
"promocodes" : [
{
"code" : "K1H5E1F1",
"price" : 265.5
},
{
"code" : "LKDS3534K",
"price" : 357
},
{
"code" : "A3B9A45KL",
"price" : 327.5
}
]
},
"inner_hits" : { -----> inner hits contains nested data
"promocodes" : {
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ -----> returns one matched field
{
"_index" : "index4",
"_type" : "_doc",
"_id" : "NTBFgm0BFLPFo7KPt70j",
"_nested" : {
"field" : "promocodes",
"offset" : 0
},
"_score" : null,
"_source" : {
"code" : "K1H5E1F1",
"price" : 265.5
},
"sort" : [
265.5
]
}
]
}
}
}
}
]
EDIT:
Below logic checks if promocode has match then return document with promocode value in innerhits. If promocode has no match and parent price is in range(gte and lte value) then return that document.
GET dev_products/_search
{
"_source": "price",
"query": {
"bool": {
"should": [
{
"bool": {
"must": [
{
"range": {
"price": {
"gte": 100,
"lte": 350
}
}
}
],
"must_not": [
{
"nested": {
"path": "promocodes",
"query": {
"bool": {
"must": [
{
"terms": {
"promocodes.code.keyword": [
"K1H5E1F1",
"A3B9A45KL"
]
}
}
]
}
},
"inner_hits": {
"sort": {
"promocodes.price": "asc"
},
"size": 1
}
}
}
]
}
},
{
"nested": {
"path": "promocodes",
"query": {
"bool": {
"must": [
{
"terms": {
"promocodes.code.keyword": [
"K1H5E1F1",
"A3B9A45KL"
]
}
},
{
"range": {
"promocodes.price": {
"gte": 100,
"lte": 350
}
}
}
]
}
},
"inner_hits": {
"sort": {
"promocodes.price": "asc"
},
"size": 1
}
}
}
]
}
}
}
EDIT-2
Query
GET dev_products/_search
{
"_source": "price",
"query": {
"bool": {
"should": [
{
"bool": {
"must": [
{
"range": {
"price": {
"gte": 100,
"lte": 350
}
}
}
],
"must_not": [
{
"nested": {
"path": "promocodes",
"query": {
"bool": {
"must": [
{
"terms": {
"promocodes.code.keyword": [
"K1H5E1F1",
"A3B9A45KL"
]
}
}
]
}
}
}
}
]
}
},
{
"bool": {
"must": [
{
"nested": {
"path": "promocodes",
"query": {
"bool": {
"must": [
{
"terms": {
"promocodes.code.keyword": [
"K1H5E1F1",
"A3B9A45KL"
]
}
},
{
"range": {
"promocodes.price": {
"lte": 350,
"gte": 100
}
}
}
]
}
},
"inner_hits": {
"sort": {
"promocodes.price": "asc"
},
"size": 1
}
}
}
],
----> don't include document if any matched promcode has value less than given range
"must_not": [
{
"nested": {
"path": "promocodes",
"query": {
"bool": {
"must": [
{
"terms": {
"promocodes.code.keyword": [
"K1H5E1F1",
"A3B9A45KL"
]
}
},
{
"range": {
"promocodes.price": {
"lt": 100
}
}
}
]
}
}
}
}
]
}
}
]
}
}
}
If the range for price and promocodes.price is equal to "gte":270,"lte":271 and in terms of promocodes.code is equal to ["promo1","promo2","promo4"] the request does not work - in fact, he should not choose this product, since the price according to the lowest promotional code is 265.5 and does not fall into the diapason of range, but he still selects this product and does not add the desired Promocode for inner_hits (for some reason he chooses "promo2" for inner_hits with price 270).
"price": 275,
"promocodes" : [
{
"code" : "promo1",
"price" : 265.5
},
{
"code" : "promo2",
"price" : 270
},
{
"code" : "promo3",
"price" : 250
}
]

Elasticsearch - OR in term conditions

I need little help with transfering mysql query to ES. The query looks like this
SELECT * FROM `xyz` WHERE visibility IN (1,2) AND (active=0 OR (active=1 AND finished=1)
It's easy, to make only AND conditions, but how to mix AND with OR in term?
"query" : {
"bool" : {
"must" : [{
"terms" : { "visibility" : ["1", "2"] }
}, {
"term" : { "active" : "1" }
}, {
"term" : { "active" : "0", "finished" : "1" } // OR
},]
}
}
Try like this by nesting a bool/should and bool/filter query inside the main bool/filter query:
{
"query": {
"bool": {
"filter": [
{
"terms": {
"visibility": [
"1",
"2"
]
}
},
{
"bool": {
"should": [
{
"term": {
"active": "0"
}
},
{
"bool": {
"filter": [
{
"term": {
"active": "1"
}
},
{
"term": {
"finished": "1"
}
}
]
}
}
]
}
}
]
}
}
}

Elastic Search 1.7.3 Nested filter: matching terms in an array of objects

I am trying to query for the following document in my elasticsearch:
"amenity": [
"Free Wifi",
"Free Breakfast",
"Veg Only",
"Swimming Pool",
"Newspaper",
"Bar",
"Credit Card",
"Pickup & Drop",
"Gym",
"Elevator",
"Valet Parking"
],
"dodont": [
{
"do_or_dont": "Do",
"what": "Vegetarians"
},
{
"do_or_dont": "Do",
"what": "Family"
},
{
"do_or_dont": "Dont",
"what": "Loud Music"
},
{
"do_or_dont": "Dont",
"what": "Booze"
}
]
and here is the query I have written:
"filter": {
"and": {
"filters": [
{
"nested" : {
"path" : "dodont",
"filter" : {
"bool" : {
"must": [{"and" : [
{
"term" : {"dodont.do_or_dont" : "Do"}
},
{
"term" : {"dodont.what" : "Vegetarians"}
}
]},
{"and" : [
{
"term" : {"dodont.do_or_dont" : "Do"}
},
{
"term" : {"dodont.what" : "Family"}
}
]}]
}
}
}
}
]
}
}
Now this query returns empty result, but when I change the "must" to "should" in the bool in above code, it returns the above document as the result (there is only 1 document matching this filter the one shown above), but ideally, the "must" condition should return the above document, I want to pass multiple objects for Do's and donts and I only want the results which match all of them, but I am not able to do so. How should I go about it?
You need to split out the two conditions on your nested document, since each element of the dodont nested array is conceptually a separate document:
{
"filter": {
"and": {
"filters": [
{
"nested": {
"path": "dodont",
"filter": {
"and": [
{
"term": {
"dodont.do_or_dont": "Do"
}
},
{
"term": {
"dodont.what": "Vegetarians"
}
}
]
}
}
},
{
"nested": {
"path": "dodont",
"filter": {
"and": [
{
"term": {
"dodont.do_or_dont": "Do"
}
},
{
"term": {
"dodont.what": "Family"
}
}
]
}
}
}
]
}
}
}

Resources