Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mongodb : Advanced conditional query

Tags:

mongodb

I have the following list of documents: ( the collection has more than 100 documents )

{name : 'Tom', gender : 'male'},
{name : 'Sandra', gender : 'female'},
{name : 'Alex', gender : 'male'}

what i want is to return only 4 records with 2 of them being male and 2 of them being female.

So far I've tried this:

db.persons.find({'gender' : { $in : ['male','female']},{$limit : 4});

which brings 4 records as expected but isn't guaranteed to have 2 male and 2 female exact. Is there any way I can filter documents to return the specified list and also not require to make two separate db calls?

Thanks in advance.

like image 490
I_Debug_Everything Avatar asked Dec 29 '25 10:12

I_Debug_Everything


2 Answers

I've been struggling to find a valid solution to your problem, but it appears that it is no easy task.

The only way that I thought of possible to call the database once is by grouping the information by gender and then project the resulted names array by slicing it and limiting the array size to 2.

This is not possible in the aggregation pipeline as you can not use operators such as $slice.

Still, I managed to group the database entries by gender and return the values in an array, which can then be manipulated.

After a lot of attempts, I came up with the below solution:

var people = db.people.aggregate([
    {
        $group: {
            _id: '$gender',
            names: { $push: '$name'}
        }
    }
]).toArray();

var new_people = [];

for (var i = 0; i < people.length; i++) {
    for (var j = 0; j < 2; j++) {
        new_people.push({
            gender: people[i]._id,
            name: people[i].names[j]
        });
    }
}

If you want to filter the data you have two options, based on my example:

  • Filter the data in the aggregation pipeline within the $match stage

  • Filter the data when looping over the aggregation resulted array

like image 115
vladzam Avatar answered Jan 04 '26 21:01

vladzam


Making two calls is easy and I can see no reason for not making them.

Collect the results of two finds:

var males = db.person.find({"gender": "male"}, {"name":1}).limit(2);
var females = db.person.find({"gender": "female"}, {"name":1}).limit(2);
var all = [];
var collectToAll = function(person) { all.push(person); };
males.forEach(collectToAll)
females.forEach(collectToAll)

Then all is

[
        {
                "_id" : ObjectId("549289765732b52ca191fdae"),
                "name" : "Tom"
        },
        {
                "_id" : ObjectId("549289865732b52ca191fdb0"),
                "name" : "Alex"
        },
        {
                "_id" : ObjectId("549289805732b52ca191fdaf"),
                "name" : "Sandra"
        }
]

Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!