Ludwig Magnusson
Ludwig Magnusson

Reputation: 14379

Find document with array that contains a specific value

If I have this schema...

person = {
    name : String,
    favoriteFoods : Array
}

... where the favoriteFoods array is populated with strings. How can I find all persons that have "sushi" as their favorite food using mongoose?

I was hoping for something along the lines of:

PersonModel.find({ favoriteFoods : { $contains : "sushi" }, function(...) {...});

(I know that there is no $contains in mongodb, just explaining what I was expecting to find before knowing the solution)

Upvotes: 731

Views: 892217

Answers (14)

jQueeny
jQueeny

Reputation: 8027

For anyone new to MongoDB and isn't familiar with constructing MongoDB JSON query objects inside the find* methods, you can leverage the mongoose Query Builders. These are great for newcomers or to encourage consistent querying within your team.

Given the following documents:

{
   "_id": "65440e1e5d9811753b6be8e1",
   "favoriteFoods": [ "corn", "sushi", "pasta" ],
},
{
   "_id": "6545612721b99c8cff188a0a",
   "favoriteFoods": [ "eggs", "pizza", "rice" ],
}

You can chain the where() and in() methods to match the specified documents:

const person = await PersonModel.find().where('favoriteFoods').in(['sushi']);
// Returns
[
   {
      "_id": "65440e1e5d9811753b6be8e1",
      "favoriteFoods": [ "corn", "sushi", "pasta" ],
   }
]

const person = await PersonModel.find().where('favoriteFoods').in(['sushi', 'pizza']);
// Returns
[
   {
      "_id": "65440e1e5d9811753b6be8e1",
      "favoriteFoods": [ "corn", "sushi", "pasta" ],
   },
   {
      "_id": "6545612721b99c8cff188a0a",
      "favoriteFoods": [ "eggs", "pizza", "rice" ],
   }
]

A full list of Query Builder methods can be found here.

Upvotes: 1

chavy
chavy

Reputation: 1068

In case You are searching in an Array of objects, you can use $elemMatch. For example:

PersonModel.find({ favoriteFoods : { $elemMatch: { name: "sushiOrAnytthing" }}});

Upvotes: 2

Hiran Walawage
Hiran Walawage

Reputation: 2185

With populate & $in this code will be useful.

ServiceCategory.find().populate({
    path: "services",
    match: { zipCodes: {$in: "10400"}},
    populate: [
        {
            path: "offers",
        },
    ],
});

Upvotes: 1

Rustamjon Kirgizbaev
Rustamjon Kirgizbaev

Reputation: 497

There are some ways to achieve this. First one is by $elemMatch operator:

const docs = await Documents.find({category: { $elemMatch: {$eq: 'yourCategory'} }});
// you may need to convert 'yourCategory' to ObjectId

Second one is by $in or $all operators:

const docs = await Documents.find({category: { $in: [yourCategory] }});

or

const docs = await Documents.find({category: { $all: [yourCategory] }});
// you can give more categories with these two approaches 
//and again you may need to convert yourCategory to ObjectId

$in is like OR and $all like AND. For further details check this link : https://docs.mongodb.com/manual/reference/operator/query/all/

Third one is by aggregate() function:

const docs = await Documents.aggregate([
    { $unwind: '$category' },
    { $match: { 'category': mongoose.Types.ObjectId(yourCategory) } }
]};

with aggregate() you get only one category id in your category array.

I get this code snippets from my projects where I had to find docs with specific category/categories, so you can easily customize it according to your needs.

Upvotes: 9

JohnnyHK
JohnnyHK

Reputation: 311835

As favouriteFoods is a simple array of strings, you can just query that field directly:

PersonModel.find({ favouriteFoods: "sushi" }, ...); // favouriteFoods contains "sushi"

But I'd also recommend making the string array explicit in your schema:

person = {
    name : String,
    favouriteFoods : [String]
}

The relevant documentation can be found here: https://docs.mongodb.com/manual/tutorial/query-arrays/

Upvotes: 1017

gautam
gautam

Reputation: 402

Incase of lookup_food_array is array.

match_stage["favoriteFoods"] = {'$elemMatch': {'$in': lookup_food_array}}

Incase of lookup_food_array is string.

match_stage["favoriteFoods"] = {'$elemMatch': lookup_food_string}

Upvotes: 10

Alingenomen
Alingenomen

Reputation: 1

If you'd want to use something like a "contains" operator through javascript, you can always use a Regular expression for that...

eg. Say you want to retrieve a customer having "Bartolomew" as name

async function getBartolomew() {
    const custStartWith_Bart = await Customers.find({name: /^Bart/ }); // Starts with Bart
    const custEndWith_lomew = await Customers.find({name: /lomew$/ }); // Ends with lomew
    const custContains_rtol = await Customers.find({name: /.*rtol.*/ }); // Contains rtol

    console.log(custStartWith_Bart);
    console.log(custEndWith_lomew);
    console.log(custContains_rtol);
}

Upvotes: -8

Amitesh Bharti
Amitesh Bharti

Reputation: 15693

Though agree with find() is most effective in your usecase. Still there is $match of aggregation framework, to ease the query of a big number of entries and generate a low number of results that hold value to you especially for grouping and creating new files.

  PersonModel.aggregate([
            { 
                 "$match": { 
                     $and : [{ 'favouriteFoods' : { $exists: true, $in: [ 'sushi']}}, ........ ]  }
             },
             { $project : {"_id": 0, "name" : 1} }
            ]);

Upvotes: 5

Mark Ryan Orosa
Mark Ryan Orosa

Reputation: 867

For Loopback3 all the examples given did not work for me, or as fast as using REST API anyway. But it helped me to figure out the exact answer I needed.

{"where":{"arrayAttribute":{ "all" :[String]}}}

Upvotes: 2

Kfir Erez
Kfir Erez

Reputation: 3470

In case that the array contains objects for example if favouriteFoods is an array of objects of the following:

{
  name: 'Sushi',
  type: 'Japanese'
}

you can use the following query:

PersonModel.find({"favouriteFoods.name": "Sushi"});

Upvotes: 126

Pobe
Pobe

Reputation: 2793

I feel like $all would be more appropriate in this situation. If you are looking for person that is into sushi you do :

PersonModel.find({ favoriteFood : { $all : ["sushi"] }, ...})

As you might want to filter more your search, like so :

PersonModel.find({ favoriteFood : { $all : ["sushi", "bananas"] }, ...})

$in is like OR and $all like AND. Check this : https://docs.mongodb.com/manual/reference/operator/query/all/

Upvotes: 157

Jesus Campon
Jesus Campon

Reputation: 452

In case you need to find documents which contain NULL elements inside an array of sub-documents, I've found this query which works pretty well:

db.collection.find({"keyWithArray":{$elemMatch:{"$in":[null], "$exists":true}}})

This query is taken from this post: MongoDb query array with null values

It was a great find and it works much better than my own initial and wrong version (which turned out to work fine only for arrays with one element):

.find({
    'MyArrayOfSubDocuments': { $not: { $size: 0 } },
    'MyArrayOfSubDocuments._id': { $exists: false }
})

Upvotes: 35

user3027146
user3027146

Reputation: 53

I know this topic is old, but for future people who could wonder the same question, another incredibly inefficient solution could be to do:

PersonModel.find({$where : 'this.favouriteFoods.indexOf("sushi") != -1'});

This avoids all optimisations by MongoDB so do not use in production code.

Upvotes: -26

Alistair Nelson
Alistair Nelson

Reputation: 3293

There is no $contains operator in mongodb.

You can use the answer from JohnnyHK as that works. The closest analogy to contains that mongo has is $in, using this your query would look like:

PersonModel.find({ favouriteFoods: { "$in" : ["sushi"]} }, ...);

Upvotes: 244

Related Questions