41

Consider a MongoDB document in users collection:

{ username : 'Alex', tags: ['C#', 'Java', 'C++'] }

Is there any way, to get the length of the tags array from the server side (without passing the tags to the client) ?

Thank you!

Penny Liu
  • 15,447
  • 5
  • 79
  • 98
Zaur Nasibov
  • 22,280
  • 12
  • 56
  • 83

6 Answers6

31

if username Alex is unique, you can use next code:

db.test.insert({username:"Alex", tags: ['C#', 'Java', 'C++'] });
db.test.aggregate(
  {$match: {username : "Alex"}}, 
  {$unwind: "$tags"},
  {$project: {count:{$add:1}}},
  {$group: {_id: null, number: {$sum: "$count" }}}
);
{ "result" : [ { "_id" : null, "number" : 3 } ], "ok" : 1 }
xmm.dev
  • 460
  • 1
  • 5
  • 5
  • 1
    Thank you. The question was asked back then when the aggregation framework was not available, but it makes perfect sense now. – Zaur Nasibov Dec 16 '12 at 16:26
20

Now MongoDB (2.6 release) supports $size operation in aggregation.

From the documentation:

{ <field>: { $size: <array> } }

What you want can be accomplished as following with either by using this:

db.users.aggregate(
   [
      {
         $group: {
            _id: "$username",
            tags_count:  {$first: {$size: "$tags" }}
         }
      }
   ]
)

or

db.users.aggregate(
   [
      {
         $project: {
            tags_count: {$size: "$tags"}
         }
      }
   ]
)
anvarik
  • 6,417
  • 5
  • 39
  • 53
  • 7
    I don't think you can use $size operator just like this with $group. $size is not among [aggregation operators](http://docs.mongodb.org/manual/reference/operator/aggregation-group/). Instead, you can use projection if you don't need grouping: db.test.aggregate({$project: count: {$size: "$tags"}}) OR if you need grouping, smth like this: db.test.aggregate({$group: {_id: "$userName", count: {$sum: {$size:"$tags"}}}}) – Volodymyr Metlyakov Jun 18 '14 at 16:53
  • How is it possible to get when I have something like this: { username : 'Alex', tags: [['C#', 'Java', 'C++']] } How can we get the length/size of tags now? – rkatkam Feb 16 '15 at 09:06
  • @rkatkam if `tags` is simply an array of strings, check the answer. If it is an array of arrays, you might want to unwind tags first – anvarik Feb 16 '15 at 10:07
  • I tried using $unwind, but that is giving me an empty list as output. See here http://stackoverflow.com/questions/28538982/mongodb-aggregate-to-get-length-of-first-nested-array-list – rkatkam Feb 16 '15 at 10:14
16

I think it might be more efficient to calculate the number of tags on each save (as a separate field) using $inc perhaps or via a job on a schedule.

You could also do this with map/reduce (the canonical example) but that doesn't seem to be be what you'd want.

I'm not sure it's possible to do exactly what you are asking, but you can query all the documents that match a certain size with $size ...

> db.collection.find({ tags : { $size: 3 }});

That'd get you all the documents with 3 tags ...

Justin Jenkins
  • 26,590
  • 6
  • 68
  • 1,285
10

xmm.dev's answer can be simplified: instead of having interm field 'count', you can sum directly in $group:

db.test.aggregate(
  {$match: {username : "Alex"}}, 
  {$unwind: "$tags"},
  {$group: {_id: null, number: {$sum: 1 }}}
) 
9

Currently, the only way to do it seems to be using db.eval, but this locks database for other operations.

The most speed-efficient way would be adding an extra field that stores the length of the array and maintaining it by $inc and $push operations.

plaes
  • 31,788
  • 11
  • 91
  • 89
  • Thank you very much. Yep, I also thought about keeping the size in a separate field, but hoped that there would be a better way. – Zaur Nasibov Jul 17 '11 at 09:37
0

I did a small work around as I needed to query the array size and return if it was greater than 0 but could be anything from 1-3.

Here was my solution:

db.test.find($or : [{$field : { $exists : true, $size : 1}},
                    {$field : { $exists : true, $size : 2}},
                    {$field : { $exists : true, $size : 3}}, ])

This basically returns a document when the attribute exists and the size is 1, 2, or 3. The user can add more statements and increment if they are looking for a specific size or within a range. I know its not perfect but it did work and was relatively quick. I only had 1-3 sizes in my attribute so this solution worked.

kwoodson
  • 959
  • 9
  • 11