'MongoDB: multiple $elemMatch

I have MongoDB documents structured like this:

{_id: ObjectId("53d760721423030c7e14266f"),
fruit: 'apple',
vitamins: [
    {
     _id: 1,
     name: 'B7',
     usefulness: 'useful',
     state: 'free',
    }
    {
     _id: 2,
     name: 'A1',
     usefulness: 'useful',
     state: 'free',
    }
    {
     _id: 3,
     name: 'A1',
     usefulness: 'useful',
     state: 'non_free',
    }
  ]
}
{_id: ObjectId("53d760721423030c7e142670"),
fruit: 'grape',
vitamins: [
    {
     _id: 4,
     name: 'B6',
     usefulness: 'useful',
     state: 'free',
    }
    {
     _id: 5,
     name: 'A1',
     usefulness: 'useful',
     state: 'non_free',
    }
    {
     _id: 6,
     name: 'Q5',
     usefulness: 'non_useful',
     state: 'non_free',
    }
  ]
}

I want to query and get all the fruits which have both {name: 'A1', state: 'non_free'} and {name: 'B7', state: 'free'}. In the worst case I want at least to count these entries if getting them is not possible and if the equivalent code exists for pymongo, to know how to write it.

For the given example I want to retrieve only the apple (first) document.

If I use $elemMatch it works only for one condition, but not for both. E.g. if I query find({'vitamins': {'$elemMatch': {'name': 'A1', 'state': 'non_free'}, '$elemMatch': {'name': 'B7', 'state': 'free'}}}) it will retrieve all the fruits with {'name': 'B7', 'state': 'free'}.



Solution 1:[1]

In this case you can use the $and-operator .

Try this query:

find({
    $and: [
         {'vitamins': {'$elemMatch': {'name': 'A1', 'state': 'non_free'} } },
         {'vitamins': {'$elemMatch': {'name': 'B7', 'state': 'free'} } }
    ]
});

To explain why you received only the result matching the second criteria: The objects inside each {} you pass to MongoDB are key/value pairs. Each key can only exist once per object. When you try to assign a value to the same key twice, the second assignment will override the first. In your case you assigned two different values to the key $elemMatch in the same object, so the first one was ignored. The query which actually arrived in MongoDB was just find({'vitamins': {'$elemMatch': {'name': 'B7', 'state': 'free'}}}).

Whenever you need to apply the same operator to the same key twice, you need to use $or or $and.

Solution 2:[2]

var fruits = db.fruits.find({
    "vitamins": {
        $all: [{
            $elemMatch: {
                "name": "A1",
                "state": "non_free"
            }
        }, {
            $elemMatch: {
                "name": "B7",
                "state": "free"
            }
        }]
    }
})

Solution 3:[3]

let query = [];
  query.push({
    id: product.id,
  });
  query.push({ date });
  for (const slot of slots) {
    query.push({
      slots: {
        $elemMatch: {
          id: slot.id,
          spots: { $gte: slot.spots },
        },
      },
    });
  }
  const cal = await Product.findOne({ $and: query });

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Mo.
Solution 3 Rafiq