Home > Back-end >  Filtering on multiple fields in an Elasticsearch ObjectField()
Filtering on multiple fields in an Elasticsearch ObjectField()

Time:12-05

I am having trouble figuring out the filtering syntax for ObjectFields() in django-elasticsearch-dsl. In particular, when I try to filter on multiple subfields of the same ObjectField(), I'm getting incorrect results.

For example, consider the following document

class ItemDocument(Document):
    product = fields.ObjectField(properties={
        'id': fields.IntegerField(),
        'name': fields.TextField(),
        'description': fields.TextField()
    })
    details = fields.ObjectField(properties={
        'category_id': fields.IntegerField(),
        'id': fields.IntegerField(),
        'value': fields.FloatField()
    })
    description = fields.TextField()

I want to find an Item with a detail object that has both category_id == 3 and value < 1.5, so I created the following query

x = ItemDocument.search().filter(Q("match",details__category_id=3) & Q("range",details__value={'lt':1.5})).execute()

Unfortunately, this returns all items which have a detail object with category_id==3 and a separate detail object with value < 1.5 e.g.

{
  "product": ...
  "details": [
    {
      "category_id": 3,
      "id": 7,
      "value": 20.0
    },
    {
      "category_id": 4,
      "id": 7,
      "value": 1.0
    },
    ...
]
}

instead of my desired result of all items that have a detail object with both category_id==3 AND value < 1.5 e.g.

{
  "product": ...
  "details": [
    {
      "category_id": 3,
      "id": 7,
      "value": 1.0
    },
    ...
]
}

How do I properly format this query using django-elasticsearch-dsl?

CodePudding user response:

You can use the nested query in Elasticsearch to filter on multiple subfields of the same ObjectField. Here is an example of how you can do this in django-elasticsearch-dsl:

x = ItemDocument.search().query(
    "nested",
    path="details",
    query=Q("match", details__category_id=3) & Q("range", details__value={'lt':1.5})
).execute()

The nested query allows you to filter on multiple subfields of the details object, and only return documents that have a detail object with both category_id==3 and value < 1.5.

You can also use the inner_hits option with the nested query to get the details of the matching detail objects:

x = ItemDocument.search().query(
    "nested",
    path="details",
    query=Q("match", details__category_id=3) & Q("range", details__value={'lt':1.5}),
    inner_hits={}
).execute()

This will add a nested field to each search result, which contains the details of the matching detail objects. You can access this field in your code like this:

results = x.to_dict()
for result in results['hits']['hits']:
    nested_results = result['inner_hits']['details']['hits']['hits']
    # do something with nested_results
  • Related