34
votes

Is it possible to get a single document on db.collection.aggregate like as in db.collection.findOne?

6
It's not really clear what you are asking for here. Specifically you should explain what you want to do. BTW the answer given so far is wrong for most cases.Neil Lunn
Did you try aggregate([....]).toArray()[0].total ?Karan Bhandari

6 Answers

40
votes

Yes, it is possible. Just add a $group stage with _id equal to null. That will calculate accumulated values for all the input documents as a whole. E.g.

{ $group: { _id: null, total: { $sum: "$price" }}}

Or if you want to get only one document from aggregated results, you can use $limit:

{ $limit: 1 }

UPDATE: Both these solutions return cursor which would have single document. But don't think about findOne as something special. It also retrieves cursor and just gets first document (if any). Here is mongo shell implementation of findOne:

function ( query , fields, options ){
    var cursor = this.find(query, fields, -1 /* limit */, 0 /* skip*/,
        0 /* batchSize */, options);

    if ( ! cursor.hasNext() )
        return null;
    var ret = cursor.next();
    if ( cursor.hasNext() ) throw "findOne has more than 1 result!";
    if ( ret.$err )
        throw "error " + tojson( ret );
    return ret;
}

As you can see, it internally uses find. So, if you want to get single document instead of cursor with single document, you can write your own function which does same with aggregate. E.g.

> DBCollection.prototype.aggregateOne = function(pipeline) {
     var cur = this.aggregate(pipeline);
     if (!cur.hasNext())
         return null; 
     return cur.next(); 
 }

Usage:

> db.collection.aggregateOne(...)
26
votes

It is possible to add $match stage to aggregation pipeline. But even if it will match only one single document, then result will still be a list (of length one in that case). So the answer is "NO, it is not possible".

3
votes

yes it is possible. you need to use mongodb $match operation

ex: this is worked for me.

{ $lookup: { from: 'user', localField: 'userId', foreignField: 'id', as: 'publisherDetails' } },
{ $match: { id } }

mondodb doc's example:

db.articles.aggregate(
[ { $match : { id : "132ada123aweae1321awew12" } },
  { $lookup: { from: 'user', localField: 'userId', foreignField: 'id', as: 'publisherDetails' } } ]
);
2
votes

If "by a single result" you mean a findOne style return type, it should be impossible to do so. While not saying that it is impossible, it should be and should be treated as such. Aggregate operations are documented to return aggregate cursors only, so the only robust conclusion to an aggregate operation should be either an aggregate cursor or an error. When you receive your aggregate cursor, you can then use its exposed methods to access your single document. To expect anything else is akin to courting chaos.

Ps: Arrived massively late to this thread. Hope to help others who might end up here later on.

0
votes

In Python you could convert the result to a list and get the first element:

result = list(mongo.db.collection.aggregate([{"$sample": {"size": 1}}]))[0]
0
votes

Just to improve the answer with another simple solution, and to avoid writing a lot of repeated code, this is my solution based on @Sergey Berezovskiy answer's.

import { Collection } from 'mongodb';

Collection.prototype.aggregateOne = async function(pipeline, options) {
const items = await this.aggregate(pipeline, options).toArray();
if (items.length === 0)
    return null; 
return items[0]; 
}

And just like normal Aggregate function you can use like this:

return db.articles.aggregateOne(
   [ { $match : { id : "132ada123aweae1321awew12" } },
   { $lookup: { from: 'user', localField: 'userId', foreignField: 'id', as: 'publisherDetails' } } ]
);

And this will return the first document if exist or null.

Hope this will facilitate life of someone rsrs.