Populate a single ObjectId reference after aggregating with mongoDb and mongoose

Viewed 322

I am trying to populate after aggregating and counting.

These are mye models:

UserLog

const UserLog = new Schema(
  {
    title: {
      type: String,
      required: true,
      trim: true,
    },
    article: {
      type: mongoose.Schema.ObjectId,
      ref: 'Article',
      required: true,
    },
    user: {
      type: mongoose.Schema.ObjectId,
      ref: 'User',
      required: true,
    },
  },
  { timestamps: true, toJSON: { virtuals: true }, toObject: { virtuals: true } }
);

Article

const Article = new Schema(
  {
    title: {
      type: String,
      required: true,
      trim: true,
    },
    content: {
      type: String,
      required: true,
    },
  },
  { timestamps: true, toJSON: { virtuals: true }, toObject: { virtuals: true } }
);

And I am running a query like this:

export const getCountByArticle = async () => {
  const articles = await UserLog.aggregate([
    {
      $group: { _id: '$article', count: { $sum: 1 } },
    },
  ]);
  return articles;
};

The query returns this:

[
        {
            "_id": "5fcbc12dd895b528289723e6",
            "count": 1
        },
        {
            "_id": "5fccefc375c47a3fd49c083d",
            "count": 2
        }
    ],

The problem is that i want to "populate" the _id field with the actual article. I have tried $lookup, but that returns an array with all of the articles, and I dont want that. Unwind is not working for me either. Under is how i actually want the result to be.

[
        {
            "_id": "5fcbc12dd895b528289723e6",
            "title": "Title",
            "content": "contentcontentcontentcontentcontentcontentcontentcontentcontentcontentcontentcontentcontentcontentcontentcontentcontentcontentcontentcontentcontent",
            "count": 1
        },
        {
            "_id": "5fccefc375c47a3fd49c083d",
            "title": "Title",
            "content": "contentcontentcontentcontentcontentcontentcontentcontentcontentcontentcontentcontentcontentcontentcontentcontentcontentcontentcontentcontentcontent",
            "count": 2
        }
    ],

I really hope that someone can help me with this. Thanks in advance.

2 Answers

You can add a $lookup pipeline step after the $group stage in your aggregate operation that does a similar job to Mongoose's populate and reshape your documents stream using $mergeObjects in the $replaceRoot to merge the joined documents from $group and $lookup array:

export const getCountByArticle = async () => {
    const articles = await UserLog.aggregate([
        { $group: { 
            _id: '$article', count: { $sum: 1 } 
        } },
        { $lookup: {
            from: 'articles',
            localField: '_id',    
            foreignField: '_id',  
            as: 'articles'
        } },
        { $replaceRoot: { 
            newRoot: { 
                $mergeObjects: [ 
                    { $arrayElemAt: [ '$articles', 0 ] }, 
                    '$$ROOT' 
                ] 
            } 
        } },
        { $project: { articles: 0 } }
    ]).exec();
  
    return articles;
};

Use $lookupfor MongoDB > 3.2

{ 
   "$lookup" : {
        from: "name of lookup collection",
        localField: "field in the input collection",
        foreignField: "field in the lookup collection",
        as: "field name for the output documents"
     }
 }
Related