How to iterate a nested type in ElasticSearch inside a filter script?

41 Views Asked by At

I have an index that stores ecommerce products. One product has lots of variants, which store the variants' price. The price is also a nested object, as it stores the price along with a date_since variable to indicate when that price started or will start applying to the variant. Here is a basic example:

{
 "product_id": "123",
    "product_variants":[
        {
            "variant_id": "456",
            "variant_prices":[
                {
                    "price": 100,
                    "date_since": 1708439263876282,
                    "date_created": 1708439263876282
                },
                {
                    "price": 90,
                    "date_since": 1708517400328610,
                    "date_created": 1708517400328610
                }
            ]
        },
        {
            "variant_id": "789",
            "variant_prices": [
                {
                    "price": 90,
                    "date_since": 1708439263876282,
                    "date_created": 1708439263876282
                },
                {
                    "price": 83,
                    "date_since": 1708517400328610,
                    "date_created": 1708517400328610
                }
            ]
        }
    ]
 }

The challenge is to filter products by price, where the price is defined as the variant_prices.price with it's respective date_since most recent, but in the past. I need to also get inner_hits ideally, indicating which product variants caused the document hit. Is this even possible?

So I can't index the current_price, as it depends on when you are asking. (Maybe there are future prices and in a couple of minutes the filter will not return the same products)

I have tried the following query:

GET /products_with_variants/_search
{
  "size": 10,
  "query": {
    "bool": {
      "must": [
        {
          "script": {
            "script": {
              "lang": "painless",
              "source": """
                for (item in params['_source']['product_variants']){
                  long price = 0;
                  long date_since = 0;
                  long date_created = 0;
                  for(item2 in item["variant_prices"]) {
                    if (item2["date_since"] > date_since) {
                      date_since = item2["date_since"];
                      price = item2["price"];
                      date_created = item2["date_created"];
                    }
                    if (item2["date_since"] == date_since && item2["date_created"] > date_created) {
                      date_since = item2["date_since"];
                      price = item2["price"];
                      date_created = item2["date_created"];
                    }
                  }
                  if (price >= params.priceMin){
                      return true;
                  }
                }
                return false;
              """,
              "params": {
                "priceMin": 10000
              }
            }
          }
        }
      ]
    }
  }
}

In response, I have gotten:

{
    ...
    "script_stack": [
        "for (item in params['_source']['product_variants']){\r\n                                long ",
        "                   ^---- HERE"
    ],
    ...
    "caused_by": {
        "type": "null_pointer_exception",
        "reason": "Cannot invoke \"Object.getClass()\" because \"callArgs[0]\" is null"
     }

}

I have also tried iterating trough doc["product_variants"] and params._source.product_variants with no success.

Any help is greatly appreciated!

1

There are 1 best solutions below

1
Murat K. On

There is a missing semicolon here:

if (price >= params.priceMin){
   return true;
}

And as I understood, you are trying to reach a document which is a null. Maybe it would be better to write an if statement to make a null check right after for loops.

if (item!= null && variant.containsKey("variant_prices"))