Khalid Abdlqader
Khalid Abdlqader

Reputation: 309

(mongo) How could i get the documents that have a value in array along with size

I have a mongo collection with something like the below:

 { 
    "_id" : ObjectId("59e013e83260c739f029ee21"), 
    "createdAt" : ISODate("2017-10-13T01:16:24.653+0000"), 
    "updatedAt" : ISODate("2017-11-11T17:13:52.956+0000"), 
    "age" : NumberInt(34), 
    "attributes" : [
        {
            "year" : "2017", 
            "contest" : [
                {
                    "name" : "Category1", 
                    "division" : "Department1"
                }, 
                {
                    "name" : "Category2", 
                    "division" : "Department1"
                }
            ]
        }, 
        {
            "year" : "2016", 
            "contest" : [ 
                {
                    "name" : "Category2", 
                    "division" : "Department1"
                }
            ]
        }, 
        {
            "year" : "2015", 
            "contest" : [
                {
                    "name" : "Category1", 
                    "division" : "Department1"
                }
            ]
        }
    ], 
    "name" : {
        "id" : NumberInt(9850214), 
        "first" : "john", 
        "last" : "afham"
    }
}

now how could i get the number of documents who have contest with name category1 more than one time or more than 2 times ... and so on

I tried to use size and $gt but couldn't form a correct result

Upvotes: 0

Views: 64

Answers (3)

dnickless
dnickless

Reputation: 10918

Assuming that a single contest will never contain the same name (e.g. "Category1") value more than once, here is what you can do.

The absence of any unwinds will result in improved performance in particular on big collections or data sets with loads of entries in your attributes arrays.

db.collection.aggregate({
    $project: {
        "numberOfOccurrences": {
            $size: { // count the number of matching contest elements
                $filter: { // get rid of all contest entries that do not contain at least one entry with name "Category1"
                    input: "$attributes",
                    cond: { $in: [ "Category1", "$$this.contest.name" ] }
                }
            }
        }
    }
}, {
    $match: { // filter the number of documents
        "numberOfOccurrences": {
            $gt: 1 // put your desired min. number of matching contest entries here
        }
    }
}, {
    $count: "numberOfDocuments" // count the number of matching documents
})

Upvotes: 2

Andriy Simonov
Andriy Simonov

Reputation: 1288

First, we need to flatten the document by the attributes and contest fields. Then to group by the document initial _id and a contest names counting different contests along the way. Finally, we filter the result.

db.person.aggregate([
    { $unwind: "$attributes" },
    { $unwind: "$attributes.contest" },

    {$group: {
              _id: {initial_id: "$_id", contest: "$attributes.contest.name"},
              count: {$sum: 1}
             }
    },
    {$match: {$and: [{"_id.contest": "Category1"}, {"count": {$gt: 1}}]}}]);

Upvotes: 0

Buzz Moschetti
Buzz Moschetti

Reputation: 7558

Try this on for size.

db.foo.aggregate([
  // Start with breaking down attributes:
  {$unwind: "$attributes"}

  // Next, extract only name = Category1 from the contest array.  This will yield                   
  // an array of 0 or 1 because I am assuming that contest names WITHIN
  // the contest array are unique.   If found and we get an array of 1, turn that                   
  // into a single doc instead of an array of a single doc by taking arrayElemAt 0.                 
  // Otherwise, "x" is not set into the doc AT ALL.  All other vars in the doc
  // will go away after $project; if you want to keep them, change this to
  // $addFields:
  ,{$project: {x: {$arrayElemAt: [ {$filter: {
            input: "$attributes.contest",
            as: "z",
            cond: {$eq: [ "$$z.name", "Category1" ]}
                }}, 0 ]}
  }}

  // We split up attributes before, creating multiple docs with the same _id.  We                   
  // must now "recombine" these _id (OP said he wants # of docs with name).
  // We now have to capture all the single "x" that we created above; docs without                  
  // Category1 will have NO "x" and we don't want to include them in the count.                     
  // Also, we KNOW that name can only be Category 1 but division could vary, so                     
  // let's capture that in the $push in case we might want it:
  ,{$group: {_id: "$_id", x: {$push: "$x.division"}}}

  // One more pass to compute length of array:
  ,{$addFields: {len: {$size: "$x"}} }

  // And lastly, the filter for one time or two times or n times:
  ,{$match: {len: {$gt: 2} }}

  ]);

Upvotes: 0

Related Questions