I have the following ElasticSearch data structure for products in a webshop:
{
"_index": "vue_storefront_catalog_1_product_1617378559",
"_type": "_doc",
"_source": {
"configurable_children": [
{
"price": 49.99,
"special_price": 34.99,
"special_from_date": "2020-11-27 00:00:00",
"special_to_date": "2020-11-30 23:59:59",
"stock": {
"qty": 0,
"is_in_stock": false,
"stock_status": 0
}
}
{
"price": 49.99,
"special_price": null,
"special_from_date": null,
"special_to_date": null,
"stock": {
"qty": 0,
"is_in_stock": false,
"stock_status": 0
}
}
]
}
Using the following mapping:
{
"vue_storefront_catalog_1_product_1614928276" : {
"mappings" : {
"properties" : {
"configurable_children" : {
"properties" : {
"price" : {
"type" : "double"
},
"special_from_date" : {
"type" : "date",
"format" : "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
},
"special_price" : {
"type" : "double"
},
"special_to_date" : {
"type" : "date",
"format" : "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
},
}
}
}
}
}
}
I have created a Elasticsearch query to filter out only products that are in sale, that means: the special_price must be lower than the price and the current date must be between the special_from_date and special_to_date.
This is the Painless script I have created:
boolean hasSale = false;
long timestampNow = new Date().getTime();
if (doc.containsKey('configurable_children.special_from_date') && !doc['configurable_children.special_from_date'].empty) {
long timestampSpecialFromDate = doc['configurable_children.special_from_date'].value.toInstant().toEpochMilli();
if (timestampSpecialFromDate > timestampNow) {
hasSale = false;
}
} else if (doc.containsKey('configurable_children.special_to_date') && !doc['configurable_children.special_to_date'].empty) {
long timestampSpecialToDate = doc['configurable_children.special_to_date'].value.toInstant().toEpochMilli();
if (timestampSpecialToDate < timestampNow) {
hasSale = false;
}
} else if (doc.containsKey('configurable_children.stock.is_in_stock') && doc['configurable_children.stock.is_in_stock'].value == false) {
hasSale = false;
} else if (1 - (doc['configurable_children.special_price'].value / doc['configurable_children.price'].value) > params.fraction) {
hasSale = true;
}
return hasSale
This returns the product as soon as one of the configurable_children has met the criteria to be a sale product. This is incorrect, because I need to loop through the whole set op configurable_children to determine if it's a sale product. How can I make sure all children are taken into the calculation? With a loop?
Here is the new query as suggested by Joe in the answers:
GET vue_storefront_catalog_1_product/_search
{
"query": {
"function_score": {
"query": {
"match_all": {}
},
"functions": [
{
"script_score": {
"script": {
"source": """
int allEntriesAreTrue(def arrayList) {
return arrayList.stream().allMatch(Boolean::valueOf) == true ? 1 : 0
}
ArrayList childrenAreMatching = [];
long timestampNow = params.timestampNow;
ArrayList children = params._source['configurable_children'];
if (children == null || children.size() == 0) {
return allEntriesAreTrue(childrenAreMatching);
}
for (config in children) {
if (!config.containsKey('stock')) {
childrenAreMatching.add(false);
continue;
} else if (!config['stock']['is_in_stock']
|| config['special_price'] == null
|| config['special_from_date'] == null
|| config['special_to_date'] == null) {
childrenAreMatching.add(false);
continue;
}
if (config['special_from_date'] != null && config['special_to_date'] != null) {
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
def from_millis = sf.parse(config['special_from_date']).getTime();
def to_millis = sf.parse(config['special_to_date']).getTime();
if (!(timestampNow >= from_millis && timestampNow <= to_millis)) {
childrenAreMatching.add(false);
continue;
}
}
def sale_fraction = 1 - (config['special_price'] / config['price']);
if (sale_fraction <= params.fraction) {
childrenAreMatching.add(false);
continue;
}
childrenAreMatching.add(true);
}
return allEntriesAreTrue(childrenAreMatching);
""",
"params": {
"timestampNow": 1617393889567,
"fraction": 0.1
}
}
}
}
],
"min_score": 1
}
}
}
The response is as follows:
{
"took" : 15155,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 2936,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [... hits here ...]
}
}
Any idea why the query takes around 15 seconds?
configurable_children
mapped as anested
field type? – Selfpropelledproperties
underneath. I have added it to the post. It would be great if you have a lead! – Cletacleti