Search code examples
arraysjsonelasticsearchnestedelasticsearch-painless

Reverse a match or match every element in an array where some IDs are not set


Struggling with making this ES query. Basically, I have a nested object, something like:

{
  "id": "00000000-0000-0000-0000-000000000000",
  "exchangeRate": 0.01,
  "payments": [
    {
      "id": "00000000-0000-0000-0000-000000000000",
      "paymentId": "some-id",
      "currency": "USD",
      "amount": 400.0
    },
    {
      "id": "00000000-0000-0000-0000-000000000000",
      "currency": "USD",
      "paymentId": "some-id2",
      "amount": -200.0
    },
    {
      "id": "00000000-0000-0000-0000-000000000000",
      "currency": "USD",
      "amount": -200.0
    }
  ]
}

And I want to match on an object where some of the "paymentId" keys are defined, but not all. So the above object would be a match. Whereas something like:

{
  "id": "00000000-0000-0000-0000-000000000000",
  "exchangeRate": 0.01,
  "payments": [
    {
      "id": "00000000-0000-0000-0000-000000000000",
      "paymentId": "some-id",
      "currency": "USD",
      "amount": 400.0
    },
    {
      "id": "00000000-0000-0000-0000-000000000000",
      "currency": "USD",
      "paymentId": "some-id2",
      "amount": -200.0
    },
    {
      "id": "00000000-0000-0000-0000-000000000000",
      "currency": "USD",
      "paymentId": "some-id3",
      "amount": -200.0
    }

Would not match.

I've made a query which matches if all paymendIds are defined and returns all objects where that is true. This query is:

{
  "query": {
    "bool": {
      "must": {
        "nested": {
          "path": "payments",
          "query": {
            "exists": {
              "field": "payments.paymendIds"
            }
          }
        }
      }
    }
  }
}

The question would be how do I reverse this? So that if it matches this query, it doesn't return as a match. As putting "must_not" simply does the opposite. It returns all records that don't have any paymentIds defined at all. Which is something I want to match on, but I need all the ones that even have only some of the paymentIds set.


Solution

  • You could compare the payments objects' field value sizes one by one while in the nested context.

    Assuming both payments.id and payments.paymentId are of the keyword mapping type, you could say:

    GET your-index/_search
    {
      "query": {
        "nested": {
          "path": "payments",
          "query": {
            "script": {
              "script": "doc['payments.id'].size() != doc['payments.paymentId'].size()"
            }
          }
        }
      }
    }