1

I have a collection with documents in the following format:

{ 
    "_id" : ObjectId("abc"), 
    "mobile" : "123456789", 
    "channels" : [
        {
            "name" : "email.xz", 
            "type" : "a", 
            "properties" : [
                {
                    "provider" : "outlook"
                }
            ]
        }, 
        {
            "name" : "sms", 
            "type" : "sms"
            }
    ], 

}

I need to find customers who have at least one channel which the name ends with ".xz", but only if provider exists and has a value, and return the count of customers per channel. For instance:

email.xz = 10
sms.xz = 5
push.xz = 7

I tried to use the aggregate function below, but it resulted in a count of all the customers that match the criteria.

db.consumers.aggregate(
{$match: {"channels.name": {$regex: ".xz$"}, 
"notificationChannels.properties.provider": { $exists: true }}},
{$group: {_id: "channels.name"},
count: {$sum: 1}})

1 Answer 1

2

$unwind the array, and then $match the same way again. The first match is only selecting documents, and you want to filter:

db.consumers.aggregate(
  { $match: {
    "channels.name": {$regex: ".xz$"}, 
    "channels.properties.provider": { $exists: true }
  }},
  { $unwind: "$channels" },
  { $match: {
    "channels.name": {$regex: ".xz$"}, 
    "channels.properties.provider": { $exists: true }
  }},
  { $group:{
    "_id": "$channels.name",
    "count": {$sum: 1}
  }}
)

Since you want to $group on a key from "inside" the array, then you need to $unwind it anyway. Since you only want the array entries that match your conditions, then the only way to do that with a $regex is through a $match after the $unwind

Sign up to request clarification or add additional context in comments.

5 Comments

$match is redundant in this query
@kailashyogeshwar No it certainly is not. It is of the "utmost importance" that you always use an initial stage in any aggregation pipeline that can address an index and ideally reduce results. You appear to be presuming that "every document" will meet the condition and that you only ever need the subsequent $match following $unwind to filter the array content. Unfortunately you probably picked up that opinion from seeing very bad examples before that do not consider that. Both $match stages here are doing two different things. So it's not a mistake but intentional and good design.
perhaps using '_id: "$channels.name"' for the $group, would be more informative?
@GerardH.Pille Thanks. Overzealous copy and paste and did not notice that.
Thank you. Never worked with MongoDB, got nowhere with this exercise after a whole day of trying. The fact that I didn't realize I was using a 3.2 (on Debian) but looking at a 3.6 manual, didn't improve my chances. Your answer should appear in the tutorials.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.