Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mongoose, apply `.aggregate()` on array of subdocuments and get the result with other fields of document in least number of queries

This is my Mongoose Model:

const postSchema = new Schema({
    user: {
        type: Schema.Types.ObjectId,
        ref: 'User',
        required: true
    },

    caption: {
        type: String
    },

    action: {
        type: [{
            actionName: {
                type: String,
                required: true
            },
            user: {
                type: Schema.Types.ObjectId,
                ref: 'User'
            }
        }],
        default: []
    },

    shares: [{
        type: Schema.Types.ObjectId,
        ref: 'User'
    }];
});

All I want is to have a mongodb query with or without using .aggregate() to get the user & caption field as it is but instead of action and shares I want their counts for a particular document.

Sample Document

{
    _id: "fgsergehegoieofgesfglesfg",

    user: "dfjksjdnfkjsdfkjsdklfjglkbj",

    caption: "This is the post caption",

    action: [
        {
            actionName: 'e1', user: "sdfasdsdfasdfdsdfac951e5c"
        },
        {
            actionName: 'e1', user: "asdfmadfadfee103c9c951e5d"
        },
        {
            actionName: 'e2', user: "op34937cdbae0cd4160bbec"
        },
        {
            actionName: 'e2', user: "2543ebbasdfd1750690b5b01c"
        },
        {
            actionName: 'e3', user: "asdfcfebdb5dd1750690b5b01d"
        },
    ],

    shares: ["ewrebdb5ddadsf5069sadf1d", "asdfsdfbb85dd1750690b5b01c", "fasec92dsfasde103c9c95df5d"]
};

Desired output after query:

{
    _id: "fgsergehegoieofgesfglesfg",
    user: 'dfjksjdnfkjsdfkjsdklfjglkbj',
    caption: 'This is the post caption',
    actionCount: [{ count: 1, actionName: 'e3' },
                 { count: 2, actionName: 'e2' },
                 { count: 2, actionName: 'e1' }],
    shareCount: 3
}

I am able do get following results using .aggregate():

Query:

let data = await Test.aggregate([
            { $match: { _id: mongoose.Types.ObjectId("fgsergehegoieofgesfglesfg") } },
            { $unwind: "$action" },
            {
                $group: {
                    _id: "$action.actionName",
                    count: { $sum: 1 }
                }
            },
            {
                $project: {
                    _id: 0,
                    actionName: "$_id",
                    count: 1
                }
            }
        ]);

Result:

[
  { count: 1, actionName: 'e3' },
  { count: 2, actionName: 'e2' },
  { count: 2, actionName: 'e1' } 
]

I just want to put this in the original document and get the result. Also, doing the same for share field. It would be better if this can be done in single query. I have tried using $replaceRoot along with $mergeObjects but don't know how to correctly use them. I am very new to mongodb and mongoose.

Please help. Thank you.

like image 211
Neetigya Chahar Avatar asked Nov 30 '25 08:11

Neetigya Chahar


1 Answers

Since you're aggregating a nested array you need to run $grouptwice and $first can be used to preserve original document's field values:

await Test.aggregate([
    { $match: { _id: mongoose.Types.ObjectId("fgsergehegoieofgesfglesfg") } },
    { $unwind: "$action" },
    {
        $group: {
            _id: { _id: "$_id", actionName: "$action.actionName" },
            user: { $first: "$user" },
            caption: { $first: "$caption" },
            count: { $sum: 1 },
            shareCount: { $first: { $size: "$shares" } }
        }
    },
    {
        $group: {
            _id: "$_id._id",
            user: { $first: "$user" },
            caption: { $first: "$caption" },
            shareCount: { $first: "$shareCount" },
            actionCount: {
                $push: {
                    actionName: "$_id.actionName",
                    count: "$count"
                }
            }
        }
    }
])

Mongo Playground

like image 171
mickl Avatar answered Dec 01 '25 22:12

mickl