harryk
harryk

Reputation: 153

MongoDB aggregation: how to find out if an array contains multiple elements

I have following data.

[
  {
    "_id": 1,
    "array": [
      {
        "name": "name1",
        "nestedArray": [
          {
            "key": "key1",
            "value": "value1"
          },
          {
            "key": "key2",
            "value": "value2"
          }
        ]
      },
      {
        "name": "name2",
        "nestedArray": [
          {
            "key": "key1",
            "value": "abc"
          },
          {
            "key": "key2",
            "value": "value2"
          }
        ]
      }
    ]
  }
]

playground I want to keep the entry whose nestedArray contains 2 matching elements and remove others. The 2 elements are below

{
    "key": "key1",
    "value": "abc"
},
{
    "key": "key2",
    "value": "value2"
}

So that the result will be below

[
  {
    "_id": 1,
    "array": [
      {
        "name": "name2",
        "nestedArray": [
          {
            "key": "key1",
            "value": "abc"
          },
          {
            "key": "key2",
            "value": "value2"
          }
        ]
      }
    ]
  }
]

The one whose name="name1" is removed since it has only one matching element. Feels like we could use $elemMatch but couldn't figure it out.

Upvotes: 2

Views: 2378

Answers (2)

harryk
harryk

Reputation: 153

First, Unwind the array so that you can easily access the nestedArray .

Second, use $all and $elementMatch on nestedArray

db.collection.aggregate([
  {
    $unwind: "$array"
  },
  {
    $match: {
      "array.nestedArray": {
        $all: [
          {
            "$elemMatch": {
              key: "key1",
              value: "abc"
            }
          },
          {
            "$elemMatch": {
              key: "key2",
              value: "value2"
            }
          }
        ]
      }
    }
  },
  {
    $group: {
      _id: "$_id",
      array: {
        "$push": "$array"
      }
    }
  }
])

playground

Upvotes: 1

Tom Slabbaert
Tom Slabbaert

Reputation: 22296

You didn't really specify how you input looks but the strategy will stay the same regardless, We will iterate over the array with $filter and match only documents that their subarray's size is equal to a filtered subarray based on the given input, like so:

// the current input structure.
inputArray = [
    {
        "key": "key1",
        "value": "abc"
    },
    {
        "key": "key2",
        "value": "value2"
    }
];
db.collection.aggregate([
  {
    $project: {
      array: {
        $filter: {
          input: "$array",
          as: "element",
          cond: {
            $and: [
              {
                $eq: [
                  {
                    $size: {
                      $filter: {
                        input: "$$element.nestedArray",
                        as: "nested",
                        cond: {
                          "$setIsSubset": [
                            [
                              "$$nested"
                            ],
                            inputArray
                          ]
                        }
                      }
                    },
                    
                  },
                  {
                    $size: "$$element.nestedArray"
                  }
                ]
              },
              {  // this is required for an exact match otherwise subsets are possible, if you wan't to allow it delete this equality completly.
                $eq: [
                  {
                    $size: "$$element.nestedArray"
                  },
                  {
                    $size: inputArray
                  }
                ]
              }
            ]
          }
        }
      }
    }
  }
])

Again if the input is coming in a different structure you'll have to change the $eq conditions

Upvotes: 0

Related Questions