Search code examples
node.jsmongodbexpressmongoosemongodb-query

MongoDB how to form a query to get top selling products ranked for each product


js and mongoose and I find it somewhat difficult to solve this issue where I have orders schema in my mongodb database, and I want to get top selling products ranked from highest to lowest this is how a document looks like:enter image description here

so, as it can be seen there is a shopping_cart which is an array of products that can be distinguished by product _id and they each have quantity ( price is not relevant ).I've tried forming this query:

const topselling=Order.aggregate([
    {
      "$unwind": "$items"
    },
    {
      "$group": {
        "_id": "$items.itemId",
        "sum": {
          "$sum": "$items.qty"
        }
      }
    },
    {
      "$sort": {
        sum: -1
      }
    },
    {
      "$group": {
        "_id": null,
        "top_selling_products ": {
          $push: "$_id"
        }
      }
    }
  ])
  console.log(topselling)

But I get an output like this when i console log :

Aggregate {
  _pipeline: [
    { '$unwind': '$items' },
    { '$group': [Object] },
    { '$sort': [Object] },
    { '$group': [Object] }
  ],
  _model: Model { Order },
  options: {}
}

I expect an output as follows :

[
  {
    "_id": null,
    "top_selling_products ": [
      {"title":"carrots","price":100,"imageUrl":"...imageurl"...},
      {"title":"Beans","price":200,"imageUrl":"...imageurl"...},
    ]
  }
]

Solution

  • From the Model.aggregate() documentation, the query is not executed. You have to provide the callback in order to execute it.

    Either of these methods should work:

    1. Provide a callback.
    const topselling = Order.aggregate(/* Pipeline */)
      .then((res) => {
        console.log(res);
      })
      .catch((err) => {
        console.error(err);
      });
    
    1. Work with asynchronous function (await).
    const topselling = await Order.aggregate(/* Pipeline */);
    
    1. Execute the query with .exec().
    const topselling = Order.aggregate(/* Pipeline */)
      .exec();
    

    While there is something in the $last group stage as you are pushing ObjectId but not object into the top_selling_products array. The last $group should be:

    {
      "$group": {
        "_id": null,
        "top_selling_products": {
          $push: "$$ROOT"
        }
      }
    }
    

    With $$ROOT means the whole document.

    Your final output will be:

    [
      {
        "_id": null,
        "top_selling_products": [
          { "_id": ObjectId("..."), "sum": 100 },
          { "_id": ObjectId("..."), "sum": 200 }
        ]
      }
    ]