1

I'm using nodejs with mongoose library for my mongoDB interaction.

I'm building a landing page query (think Netflix page) and need to get ~15 results for each categories (think Comedy, Horror, Drama, ...).

It seems not like a great solution to send a query for each "category" and deal with the callback issues as well as a heavy request on the database for a simple page load...as illustrated below:

const categories = ['horror','drama','comedy']
let counterCallback = 0
let allShows = []
categories.map(category =>{
    Show.find({category: category},{limit: 15}, (err, shows)=>{
       if(shows){ allShows.push(shows)}
       allShows++
       if(allShows === categories.length){ return res.json(allShows) }
    })
})

I was thinking maybe of making an $or request, but I can't limit for the number for each "or"s ...

const categories = ['horror','drama','comedy']
Show.find({
    $or:categories.map(category=> {return {category: category} })
},{limit: 10})

How can I achieve a request that limits the number of results per "category"?

Thanks

2 Answers 2

2

You can take advantage of MongoDB's aggregation-framework to run complex queries, try below query :

var categories = ['horror', 'comedy', 'drama']

Show.aggregate([
    /** This $match would help to  limit docs to particular categories, as each pipeline in facet stage has to iterate to all docs in collection for thrice as 3 categories given, So at least limiting docs to needed categories. */
    { $match: { category: { $in: categories } } }, 
    {
        $facet: {
            'horror': [{ $match: { category: 'horror' } }, { $limit: 1 }],
            'comedy': [{ $match: { category: 'comedy' } }, { $limit: 1 }],
            'drama': [{ $match: { category: 'drama' } }, { $limit: 1 }]
        }
    }])

Test : MongoDB-Playground

Alternatively you can achieve that using $group as well :

Show.aggregate([
    { $match: { category: { $in: categories } } },
    { $group: { _id: '$category', shows: { $push: '$$ROOT' } } },
    { $project: { category: '$_id', _id: 0, shows: { $slice: ['$shows', 1] } } },
    /** Below two stages are optional  */
    { $group: { _id: '', data: { $push: '$$ROOT' } } },
    { $project: { _id: 0 } }
])

Test : MongoDB-Playground

Sign up to request clarification or add additional context in comments.

4 Comments

Tried the first one and it works like a charm! Is there by any chance, a way to not group them (And let my frontend handle the grouping)?
@denislexic : you mean $facet ? Or $group in second query?
In $facet. So it just sends an array of shows instead of an object for each category.
@denislexic : It will be an array because if you've limit >1 then each category of shows would hold multiple values, if you just want one, then you can have object for each category rather than array for each category, Try this :: mongoplayground.net/p/0dxeMnZqEzH
2

You may use MongoDB Aggregation framework with $group operation.

I assume you have MongoDB v4.2. For earlier versions, instead of {$replaceWith:"$data"} use {$ReplaceRoot:{newRoot:"$data"}}

Show.aggregate([
  {
    $match: {
      category: {
        $in: [
          "horror",
          "comedy",
          "drama"
        ]
      }
    }
  },
  {
    $group: {
      _id: "$category",
      data: {
        $push: "$$ROOT"
      }
    }
  },
  {
    $addFields: {
      data: {
        $slice: [
          "$data",
          15
        ]
      }
    }
  },
  {
    $unwind: "$data"
  },
  {
    $replaceWith: "$data"
  }
]).exec((err, shows) => {
    if (err) throw err;
    console.log(shows);
})

MongoPlayground

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.