30

How can I get and return the first element in an array using a Mongo aggregation?

I tried using this code:

db.my_collection.aggregate([
    { $project: {
        resp : { my_field: { $slice: 1 } }
    }}
])

but I get the following error:

uncaught exception: aggregate failed: {
    "errmsg" : "exception: invalid operator '$slice'",
    "code" : 15999,
    "ok" : 0
}

Note that 'my_field' is an array of 4 elements, and I only need to return the first element.

Xavier Guihot
  • 54,987
  • 21
  • 291
  • 190
Alexandre Magro
  • 1,131
  • 2
  • 10
  • 22

5 Answers5

28

Since 3.2, we can use $arrayElemAt to get the first element in an array

db.my_collection.aggregate([
    { $project: {
        resp : { $arrayElemAt: ['$my_field',0] }
    }}
])
Xavier Guihot
  • 54,987
  • 21
  • 291
  • 190
sidgate
  • 14,650
  • 11
  • 68
  • 119
10

Currently, the $slice operator is unavailable in the the $project operation, of the aggregation pipeline. So what you could do is,

First $unwind, the my_field array, and then group them together and take the $first element of the group.

db.my_collection.aggregate([
{$unwind:"$my_field"},
{$group:{"_id":"$_id","resp":{$first:"$my_field"}}},
{$project:{"_id":0,"resp":1}}
])

Or using the find() command, where you could make use of the $slice operator in the projection part.

db.my_collection.find({},{"my_field":{$slice:1}})

Update: based on your comments, Say you want only the second item in an array, for the record with an id, id.

var field = 2;
var id = ObjectId("...");

Then, the below aggregation command gives you the 2nd item in the my_field array of the record with the _id, id.

db.my_collection.aggregate([
{$match:{"_id":id}},
{$unwind:"$my_field"},
{$skip:field-1},
{$limit:1}
])

The above logic cannot be applied for more a record, since it would involve a $group, operator after $unwind. The $group operator produces a single record for all the records in that particular group making the $limit or $skip operators applied in the later stages to be ineffective.

A small variation on the find() query above would yield you the expected result as well.

db.my_collection.find({},{"my_field":{$slice:[field-1,1]}})

Apart from these, there is always a way to do it in the client side, though a bit costly if the number of records is very large:

var field = 2; 
db.my_collection.find().map(function(doc){
return doc.my_field[field-1];
})

Choosing from the above options depends upon your data size and app design.

BatScream
  • 19,260
  • 4
  • 52
  • 68
  • You answered my question without solving my problem unfortunately. I was in need of a generic aggregate, in which a parameter defines which element of `$my_field` I'll return. I try add `$skip`, but it isn't a `$group` operator. – Alexandre Magro Nov 10 '14 at 17:09
  • See my updated answer. Anyway i thought my initial post had answered your question. If you required more clarification, you can always update your question or add a new question. – BatScream Nov 10 '14 at 18:47
2

Starting Mongo 4.4, the aggregation operator $first can be used to access the first element of an array:

// { "my_field": ["A", "B", "C"] }
// { "my_field": ["D"] }
db.my_collection.aggregate([
  { $project: { resp: { $first: "$my_field" } } }
])
// { "resp" : "A" }
// { "resp" : "D" }
Xavier Guihot
  • 54,987
  • 21
  • 291
  • 190
1

The $slice operator is scheduled to be made available in the $project operation in Mongo 3.1.4, according to this ticket: https://jira.mongodb.org/browse/SERVER-6074

This will make the problem go away.

This version is currently only a developer release and is not yet stable (as of July 2015). Expect this around October/November time.

superluminary
  • 47,086
  • 25
  • 151
  • 148
0

Mongo 3.1.6 onwards,

db.my_collection.aggregate([
{ 
    "$project": {
        "newArray" : { "$slice" : [ "$oldarray" , 0, 1 ] }
    }
}
])

where 0 is the start index and 1 is the number of elements to slice

Ali Saeed
  • 1,519
  • 1
  • 16
  • 23