Home > Back-end >  mongodb query based on multiple properties of the document which returns an array of objects that co
mongodb query based on multiple properties of the document which returns an array of objects that co

Time:09-27

I am working on a nodeJS application using MongoDB and I am struggling to construct a query. I have gone through the MongoDB documentation and the aggregation concept, but still failing to manage it.

So, I have a collection of Item documents like this:

Item schema -> { id: number, status: string, category: string, subCategory: string }

E.g.:

[
...
{
  id: 38219388214034,
  status: 'inStock',
  category: 'Food',
  subCategory: 'Sweet'
},
{
  id: 18371917455611,
  status: 'pending',
  category: 'Accessories',
  subCategory: 'Other'
}
...
]

The status can have only one of the three states: inStock, outOfStock, and pending. The subCategory has the following relation with the category (which is another collection, the Category collection):

Category schema -> { category: string, subCategory: Array<string> }

E.g.:

[
  ...
  { category: 'Food', subCategory: ['Sour', 'Sweet', 'Other'] },
  { category: 'Sports', subCategory: ['T-Shirt', 'Shorts', 'Ball', 'Other'] },
  { category: 'Accessories', subCategory: ['Drill Bits', 'Screws', 'Fluids', 'Other'] }
  ...    
 ]

What I am trying to achieve is to create a query that returns an array of objects that contain the count of each subcategory related to its category, like:

subcategory category inStock outOfStock pending items in total
Sour Food 12 1 3 16
Other Food 20 8 12 40
Other Accessories 42 12 17 71

My closest attempt to that, but still wrong, is the following:

const categories = await Categories.find();
let toMatch = [];

categories.forEach(category => {
    toMatch.push({ $and: [{ categoryName: category.categoryName }, { subCategories: category.subCategories }] });
});

const itemsCount = await Item.aggregate([
  { $match: toMatch },
  { $group: { _id: '$status', count: { $sum: 1 }} }
]);

Any help is more than appreciated!

CodePudding user response:

One option is to use $group twice for this, once to group by both subCategory and status and the second time to group only by subCategory, so the results are one document with all status options per each subCategory:

db.items.aggregate([
  {$group: {
      _id: {status: "$status", subCategory: "$subCategory"},
      category: {$first: "$category"},
      count: {$sum: 1}
  }},
  {$group: {
      _id: "$_id.subCategory",
      category: {$first: "$category"},
      totalCount: {$sum: "$count"},
      pending: {$sum: {$cond: [{$eq: ["$_id.status", "pending"]}, "$count", 0]}},
      inStock: {$sum: {$cond: [{$eq: ["$_id.status", "inStock"]}, "$count", 0]}},
      outOfStock: {$sum: {$cond: [{$eq: ["$_id.status", "outOfStock"]}, "$count", 0]}}
    }
  }
])

See how it works on the playground example

  • Related