Search code examples
elasticsearchelasticsearch-painless

How to iterate over nested fields in painless script


My document mapping looks like this:

                "_source": {
                    "id": 60,
                    "price": 60,
                    "locations": [
                      {
                         "id": 1
                         "price": 70
                      },
                      {
                         "id": 5,
                         "price": 80
                      }
                    ]
                }

So, price field can be both in root and in nested locations array. I want to be able to filter them by both values - if locationId is set in query parameters, then filter by price in locations, else - filter by root price. I wrote painless script:

            if(params['locationId'] != null){
                for (def location : doc['locations']) {
                    if (location.id == params['locationId']) {
                        if ((params['lte'] != null || location.price <= params['lte']) && (params['gte'] != null || location.price >= params['gte'])) {
                            return true;
                        } else {
                            return false;
                        }
                    }
                }
            }
            return (params['lte'] != null || params._source.price <= params['lte']) && (params['gte'] != null || params._source.price >= params['gte']);

And now I'm trying to use it:

            "query": {
                "bool": {
                    "filter": [
                        {
                            "script": {
                                "script": {
                                    "source": "my_script",
                                    "params": {
                                        "locationId": "2"
                                    }
                                }
                            }
                        }
                    ]
                }
            }

But I'm getting an error on second line of my script. No field found for [locations] in mapping with types [] How can I solve this? I also tried to access locations by params._source.locations, as I do similar in sort scripts, but params are not accessible from filters query


Solution

  • Answer #2. The bool Filter

    Your query could be transformed into unscripted

    Your filter condition is

    [(locations.id == Id) AND (locations.price in range)] OR (price in range)
    

    Unscripted bool query for the same mapping and document

    GET /outer_inner_prices/_search?filter_path=hits.hits
    {
        "query": {
            "bool": {
                "filter": [
                    {
                        "bool": {
                            "should": [
                                {
                                    "nested": {
                                        "path": "locations",
                                        "query": {
                                            "bool": {
                                                "must": [
                                                    {
                                                        "term": {
                                                            "locations.id": {
                                                                "value": 5
                                                            }
                                                        }
                                                    },
                                                    {
                                                        "range": {
                                                            "locations.price": {
                                                                "gte": 1,
                                                                "lte": 80
                                                            }
                                                        }
                                                    }
                                                ]
                                            }
                                        }
                                    }
                                },
                                {
                                    "range": {
                                        "price": {
                                            "gte": 1,
                                            "lte": 50
                                        }
                                    }
                                }
                            ]
                        }
                    }
                ]
            }
        }
    }
    

    Response

    {
        "hits" : {
            "hits" : [
                {
                    "_index" : "outer_inner_prices",
                    "_type" : "_doc",
                    "_id" : "1",
                    "_score" : 2.0,
                    "_source" : {
                        "id" : 60,
                        "price" : 60,
                        "locations" : [
                            {
                                "id" : 1,
                                "price" : 70
                            },
                            {
                                "id" : 5,
                                "price" : 80
                            }
                        ]
                    }
                }
            ]
        }
    }
    

    My recommendation is to try to re-organize your data