696

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)

ᴀʀᴍᴀɴ
  • 4,443
  • 8
  • 37
  • 57
Ludwig Magnusson
  • 13,964
  • 10
  • 38
  • 53

13 Answers13

966

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/

yaya
  • 7,675
  • 1
  • 39
  • 38
JohnnyHK
  • 305,182
  • 66
  • 621
  • 471
  • 47
    This works also if `favouriteFoods` is: `favouriteFoods:[{type:Schema.Types.ObjectId, ref:'Food'}]` – k88074 Dec 10 '14 at 19:21
  • 24
    As someone new to Mongo coming from an RDBMS like MySQL, to find that such solutions work so simply without needing JOINs and additional tables makes me wonder why I haven't started on Mongo sooner. But that's not to say either DBMS is superior over the other - it depends on your use case. – Irvin Lim Jun 18 '15 at 14:37
  • 18
    Don't mistake it. Even if it's a list of dict, you can still query it this way. Sample: `PersonModel.find({ favouriteFoods.text: "sushi" }, ...); person = { name : String, favouriteFoods : [{text:String}] }` – Aminah Nuraini Dec 14 '15 at 10:39
  • 4
    What happens when I want to find an Array that contains at least two strings? – Aero Wang Apr 07 '19 at 09:08
  • 6
    @AeroWang use this `db.person.find( { favouriteFoods: { $all: ["sushi", "sashimi"] } } )` will return all person loving both "sushi" and "sashimi" someone pointed it a few answers below – benraay Jul 01 '20 at 14:20
  • @yaya your answer is the best and easiest way to handle this query. Perhaps also worth mentioning, if you want to exclude results that have a scalar value for the specified field instead of an array, you can specify: `PersonModel.find({favouriteFoods: {$elemMatch: {$eq: "sushi"}}})` – Cutler.Sheridan Jun 02 '23 at 21:28
230

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"]} }, ...);
Alistair Nelson
  • 3,115
  • 1
  • 16
  • 15
  • 11
    Is this correct? Isn't mongodb expecting an array of values when using $in? like { name : { $in : [ "Paul", "Dave", "Larry" , "Adam"] }}? – Ludwig Magnusson Aug 09 '13 at 14:39
  • 58
    Huh? This is unnecessary. `$in` is used when you have multiple query values and the document needs to match one of them. For the reverse (which is what this question is about), JohnnyHK's answer is correct. I was going to downvote but I guess this answer may be helpful to other people who end up on this page. – MalcolmOcean Dec 19 '15 at 08:15
  • 7
    But this helped me to actually query with several values :D Many thanks ! – Alexandre Bourlier May 23 '16 at 17:05
  • 18
    Thanks. This is what I was actually looking for, the way to search for multiple values: `PersonModel.find({favouriteFoods: {"$in": ["sushi", "hotdog"]}})` – totymedli Jun 17 '16 at 12:12
  • 2
    @MalcolmOcean is correct, in that the $in operator is for the reverse, having an array as the *value*. The *field* being an array is what the question is asking about. However, if *both* the field and the value are arrays, then both this answer and JohnnyHK's are relevant, meaning you do need $in. – tscizzle Nov 03 '16 at 19:08
  • Any way to use `$in` property inside `$or` like `$or: [ { _id }, { domains: { $in: [ domains ] } } ]`? – Francis Rodrigues Dec 02 '18 at 22:41
  • @MalcolmOcean thanks for not unvoting - this answer was what i was looking for :) – dang Feb 09 '20 at 19:38
  • What does this do exactly? Does this work like this: "Query all people whose favorite foods contain e.g. sushi *and* hotdog"? Or is it "... sushi *or* hotdog"? Nvm, the next answer says $in is like *or* and $all is like *and* – EzPizza Jun 29 '21 at 09:23
151

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/

Pobe
  • 2,693
  • 1
  • 17
  • 24
  • Sorry, this is an incorrect answer to my question. I am not looking for an exact match but just for arrays that contains at least the value specified. – Ludwig Magnusson Jan 20 '17 at 08:27
  • 34
    This is a perfectly valid answer to your question! For one value there is no difference in using $all or $in. If you have several values like "sushi", "bananas", $all is looking for persons that have "sushi" AND "bananas" in their favoriteFood array, if using $in you are getting persons that have "sushi" OR "bananas" in their favorite food array. – Jodo Feb 25 '17 at 08:21
  • yeah, there is no $contains but $all is sort of it – Dee May 28 '18 at 02:27
116

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"});
Kfir Erez
  • 3,280
  • 2
  • 18
  • 17
  • 6
    This is easily the best answer. Much easier to use when you're in a hurry. – Uber Schnoz Feb 18 '19 at 18:52
  • 4
    This should be the selected answer. If you are dealing with querying an array of nested documents in MongoDB, this is how you do it. Not sure if it is the most efficient but if that's all you're trying to do, this is all you need. – Kyle L. Sep 09 '19 at 17:49
34

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 }
})
Community
  • 1
  • 1
Jesus Campon
  • 422
  • 4
  • 4
9

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}
gautam
  • 384
  • 5
  • 11
6

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.

4

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} }
            ]);
Amitesh Bharti
  • 14,264
  • 6
  • 62
  • 62
2

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]}}}

Mark Ryan Orosa
  • 847
  • 1
  • 6
  • 21
  • 1
    You are a life saver, thanks! Where is that documented and I missed it? Can you post the link please? Thanks. – user2078023 May 23 '18 at 15:36
2

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

PersonModel.find({ favoriteFoods : { $elemMatch: { name: "sushiOrAnytthing" }}});
chavy
  • 841
  • 10
  • 20
1

With populate & $in this code will be useful.

ServiceCategory.find().populate({
    path: "services",
    match: { zipCodes: {$in: "10400"}},
    populate: [
        {
            path: "offers",
        },
    ],
});
Hiran Walawage
  • 2,048
  • 1
  • 22
  • 20
-8

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);
}
-26

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.

Rob Church
  • 6,783
  • 3
  • 41
  • 46