2

Data Structure:

{
  _id: '1',
  'title: 'Blabla',
  comments: [ {id:'3', type:'normal'} , 
              {id: '4', type: 'admin'}, 
              {id: '5', type: 'admin'}, 
              {id: '6', type: 'admin'}
            ]
}

What i am trying to get is an array of last 5 comments of type admin:

{ comments: [{id:4, type:'admin'}, {id: '5', type: 'admin'}, {id: '6', type: 'admin'}] } 

What i have tried:

db.collection('posts').find({_id: '1', 'comments.type':'admin'}
                             , {
                               comments:{$slice: -5}, 
                               comments:1, _id:0
                              })

But it returns the hole comments array if at least 1 item with type admin exists.

dyk
  • 121
  • 1
  • 2
  • 12
  • Are the latest defined by incrementing IDs? Or are they just defined by their position in the array? – Sammaye May 08 '14 at 10:10
  • Then the accepted answer won't actually work, it will get the latest 5 depending upon incrementing ID – Sammaye May 08 '14 at 10:23
  • Any suggestion on how to achieve my goal? – dyk May 08 '14 at 10:25
  • This is the closest I got: http://stackoverflow.com/questions/13967569/array-subset-in-aggregation-framework-pipeline at the end of the day you might find it better to do this application side – Sammaye May 08 '14 at 10:25
  • Ok I have found something: http://stackoverflow.com/questions/12786686/can-the-mongodb-aggregation-framework-group-return-an-array-of-values the answer by Stennie there shows the use of an undocumented operator `$const` to add a field containing an array index to each element as he groups it back up, you can sort on that and then proceed with Sebastians answer – Sammaye May 08 '14 at 10:34
  • the workaround i see here is: have an insert date field on comment array and sort by it – dyk May 08 '14 at 10:39
  • Indeed normally comments would be dated, i.e. to get comments by date range – Sammaye May 08 '14 at 10:43

2 Answers2

4

You can try to use the aggregation framework:

db.collection.aggregate([ 
  { $match: {_id: "1"} }, 
  { $unwind: "$comments" }, 
  { $match: { "comments.type": "admin" } },
  { $sort: { "comments.id": -1 } }, 
  { $limit: 5 } 
])

This will result in a list of the last 5 comments for _id="1":

{ "_id" : "1", "title" : "abc", "comments" : { "id" : "7", "type" : "admin" } }
{ "_id" : "1", "title" : "abc", "comments" : { "id" : "6", "type" : "admin" } }
{ "_id" : "1", "title" : "abc", "comments" : { "id" : "5", "type" : "admin" } }
{ "_id" : "1", "title" : "abc", "comments" : { "id" : "4", "type" : "admin" } }
{ "_id" : "1", "title" : "abc", "comments" : { "id" : "3", "type" : "admin" } }
Sebastian
  • 16,813
  • 4
  • 49
  • 56
  • I see, while it does work, is it a good option for performance? If i get the hole array, and run it thought 'for' cycle in node to match my needs, would it be slower? – dyk May 08 '14 at 10:19
  • The advantage is that Mongo does not need to send the data to the client to perform the operation. Therefore doing this in the database will be faster. – Sebastian May 08 '14 at 10:25
  • MongoDB will load everything into memory, the second $match is after a $unwind, as such the entire document will be loaded into memory – Sammaye May 08 '14 at 10:27
0

You can use $elemMatch operator:

db.collection('posts').find(
    {
        _id: '1',
    },
    {
        comments: {
            $elemMatch: {
                'type': 'admin'
            }
        }
    }
)
Artem Petrosian
  • 2,964
  • 1
  • 22
  • 30