MongoDB.live, free & fully virtual. June 9th - 10th. Register Now

Is it possible to use $elemMatch within $filter?

Hi everyone,

Is it possible to use elemMatch within filter?

This is an example of my data:

{
    "_id" : ObjectId("5e6c26153facb910290f0869"),
    "attributes" : [ 
        {
            "k" : "first_name",
            "v" : "John"
        }, 
        {
            "k" : "last_name",
            "v" : "Doe"
        }, 
        {
            "k" : "email",
            "v" : "john.doe@example.net"
        },
    ],
    "events" : [ 
        {
            "event" : "add_to_cart",
            "event_data" : [ 
                {
                    "k" : "product_name",
                    "v" : "T-shirt"
                }, 
                {
                    "k" : "price",
                    "v" : 30
                }
            ],
            "created_at" : ISODate("2020-03-14T00:32:21.000Z")
        }, 
    ],
    "created_at" : ISODate("2020-03-14T00:32:21.000Z"),
}

I would like how it is possible to get users who had event “add_to_cart” with specific key/value attributes.

db.getCollection('clients').aggregate([
   {
      "$match": {
          "deleted_at": {
            "$exists": false
         }
      }
   },
   {
      "$addFields": {
         "event_count": {
            '$size': {
                 "$filter" : {
                     "input" : "$events", 
                     'as' : 'events',
                     'cond' : {
                       '$and': {
                           // How to add elemMatch here?**
                        }    
                     }
                  }    
            }
          }
       }
   },
])

How to combine $elemMatch and $filter?

I am not 100% sure. However $elemMatch would need to know the array field to operate on. As seen with the docs example

{ _id: 1, results: [ { product: "abc", score: 10 }, { product: "xyz", score: 5 } ] }
{ _id: 2, results: [ { product: "abc", score: 8 }, { product: "xyz", score: 7 } ] }
{ _id: 3, results: [ { product: "abc", score: 7 }, { product: "xyz", score: 8 } ] }
db.survey.find(
   { results: { $elemMatch: { product: "xyz", score: { $gte: 8 } } } }
)

I think you might be able to achomplish what you are after with the following.

 {
      "$addFields": {
         "event_count": {
            '$size': {
                 "$filter" : {
                     "input" : "$events", 
                     'as' : 'events',
                     'cond' : {
                       '$and': [
                          { '$$events.k': { $eq: something } },
                          { '$$events.v': { $eq: something } }
                        ]   
                     }
                  }    
            }
          }
       }
   }

Again I am not 100% sure. I just wanted to share some thoughts

Thank you for the answer.

Hmm, the problem is that it is possible to have another key-value array in v key. And another.

I also tried to use the following filter:

  "$filter" : {
         "input" : "$events", 
          "as" : "events",
          'cond' : {
              '$and': {
                 '$eq': [ '$$events.event.event_data.k', "product_name" ]
          }    
       }
  } 

However, it doesn’t return anything. Zero results. Although there should be.

If you used that exact example then the $and is incorrect as it takes an array instead of an object.
See the docs
https://docs.mongodb.com/manual/reference/operator/aggregation/and/index.html

Unfortunately, I haven’t understood you.

This also does not work:

  '$and': [
        {'$eq': [ '$$events.event.event_data.k', "product_name" ]}
  ]

Check out this example in the docs

db.inventory.aggregate(
   [
     {
       $project:
          {
            item: 1,
            qty: 1,
            result: { $and: [ { $gt: [ "$qty", 100 ] }, { $lt: [ "$qty", 250 ] } ] }
          }
     }
   ]
)

See how there are 2 expressions in the $and. If you only need one expression check then no need for the $and operation

And I think you want '$$events.event_data.k' as you are setting the events array to $$events in the $filter operation

There will be more filters, but I simplified it here. Yes, you’re right. I should be $$events.event_data.k but still nothing:

 "$filter" : {
      "input" : "$events", 
      "as": "events",
      "cond": { "$eq": [ "$$events.event_data.k", "product_name" ] }
 }    

This seems weird to me. :confused: