Mongodb check If field exists in an sub-document of an array

mongodb return only matching array elements
mongodb query subdocument array
mongodb check if value exists in array
mongodb find in array of objects
mongodb query nested array
mongodb find all elements in array
mongodb elemmatch
mongodb aggregate match in array of objects

I am trying to check If a field exists in a sub-document of an array and if it does, it will only provide those documents in the callback. But every time I log the callback document it gives me all values in my array instead of ones based on the query.

I am following this tutorial And the only difference is I am using the findOne function instead of find function but it still gives me back all values. I tried using find and it does the same thing.

I am also using the same collection style as the example in the link above.

Example In the image above you can see in the image above I have a document with a uid field and a contacts array. What I am trying to do is first select a document based on the inputted uid. Then after selecting that document then I want to display the values from the contacts array where contacts.uid field exists. So from the image above only values that would be displayed is contacts[0] and contacts[3] because contacts1 doesn't have a uid field.

Contact.contactModel.findOne({$and: [

        {uid: self.uid},

        {contacts: {

          $elemMatch: {

            uid: {

              $exists: true,

              $ne: undefined,

            }

          }

        }}

      ]}

You problems come from a misconception about data modeling in MongoDB, not uncommon for developers coming from other DBMS. Let me illustrate this with the example of how data modeling works with an RDBMS vs MongoDB (and a lot of the other NoSQL databases as well).

With an RDBMS, you identify your entities and their properties. Next, you identify the relations, normalize the data model and bang your had against the wall for a few to get the UPPER LEFT ABOVE AND BEYOND JOIN™ that will answer the questions arising from use case A. Then, you pretty much do the same for use case B.

With MongoDB, you would turn this upside down. Looking at your use cases, you would try to find out what information you need to answer the questions arising from the use case and then model your data so that those questions can get answered in the most efficient way.

Let us stick with your example of a contacts database. A few assumptions to be made here:

  1. Each user can have an arbitrary number of contacts.
  2. Each contact and each user need to be uniquely identified by something other than a name, because names can change and whatnot.
  3. Redundancy is not a bad thing.

With the first assumption, embedding contacts into a user document is out of question, since there is a document size limit. Regarding our second assumption: the uid field becomes not redundant, but simply useless, as there already is the _id field uniquely identifying the data set in question.

The use cases

Let us look at some use cases, which are simplified for the sake of the example, but it will give you the picture.

  1. Given a user, I want to find a single contact.
  2. Given a user, I want to find all of his contacts.
  3. Given a user, I want to find the details of his contact "John Doe"
  4. Given a contact, I want to edit it.
  5. Given a contact, I want to delete it.
The data models
User
{
  "_id": new ObjectId(),
  "name": new String(),
  "whatever": {}
}
Contact
{
  "_id": new ObjectId(),
  "contactOf": ObjectId(),
  "name": new String(),
  "phone": new String()
}

Obviously, contactOf refers to an ObjectId which must exist in the User collection.

The implementations

Given a user, I want to find a single contact.

If I have the user object, I have it's _id, and the query for a single contact becomes as easy as

db.contacts.findOne({"contactOf":self._id})

Given a user, I want to find all of his contacts.

Equally easy:

db.contacts.find({"contactOf":self._id})

Given a user, I want to find the details of his contact "John Doe"

db.contacts.find({"contactOf":self._id,"name":"John Doe"})

Now we have the contact one way or the other, including his/her/undecided/choose not to say _id, we can easily edit/delete it:

Given a contact, I want to edit it.

db.contacts.update({"_id":contact._id},{$set:{"name":"John F Doe"}})

I trust that by now you get an idea on how to delete John from the contacts of our user.

Notes
Indices

With your data model, you would have needed to add additional indices for the uid fields - which serves no purpose, as we found out. Furthermore, _id is indexed by default, so we make good use of this index. An additional index should be done on the contact collection, however:

db.contact.ensureIndex({"contactOf":1,"name":1})
Normalization

Not done here at all. The reasons for this are manifold, but the most important is that while John Doe might have only have the mobile number of "Mallory H Ousefriend", his wife Jane Doe might also have the email address "janes_naughty_boy@censored.com" - which at least Mallory surely would not want to pop up in John's contact list. So even if we had identity of a contact, you most likely would not want to reflect that.

Conclusion

With a little bit of data remodeling, we reduced the number of additional indices we need to 1, made the queries much simpler and circumvented the BSON document size limit. As for the performance, I guess we are talking of at least one order of magnitude.

$elemMatch (query), The $elemMatch operator matches documents that contain an array field with at least one element that matches all the specified query criteria. copy. copied. This query will select all documents in the inventory collection where the qty field exists and its value does not equal 5 or 15. Null Values ¶ The following examples uses a collection named records with the following documents:

In the tutorial you mentioned above, they pass 2 parameters to the method, one for filter and one for projection but you just passed one, that's the difference. You can change your query to be like this:

Contact.contactModel.findOne( 
    {uid: self.uid},
    {contacts: {
      $elemMatch: {
        uid: {
          $exists: true,
          $ne: undefined,
        }
      }
    }}
 )

$exists, When <boolean> is true, $exists matches the documents that contain the field, including documents where the field value is null . If <boolean> is false, the query​  Where the.aggregate () result not only matches the documents, but filters out content from the array where the field was not present, so as to just return the "id" of that particular sub-document. Then the looped.update () statements match each found array element in each document and adds the missing field with a value at the matched position.

The agg framework makes filtering for existence of a field a little tricky. I believe the OP wants all docs where a field exists in an array of subdocs and then to return ONLY those subdocs where the field exists. The following should do the trick:

var inputtedUID = "0";  // doesn't matter
db.foo.aggregate(
[
// This $match finds the docs with our input UID:
{$match: {"uid": inputtedUID }}

// ... and the $addFields/$filter will strip out those entries in contacts where contacts.uid does NOT exist.  We wish we could use {cond: {$zz.name: {$exists:true} }} but
// we cannot use $exists here so we need the convoluted $ifNull treatment.  Note we
// overwrite the original contacts with the filtered contacts:
,{$addFields: {contacts: {$filter: {
                input: "$contacts",
                as: "zz",
                cond: {$ne: [ {$ifNull:["$$zz.uid",null]}, null]}
            }}
    }}
,{$limit:1}  // just get 1 like findOne()
 ]);

show(c);

{
    "_id" : 0,
    "uid" : 0,
    "contacts" : [
        {
            "uid" : "buzz",
            "n" : 1
        },
        {
            "uid" : "dave",
            "n" : 2
        }
    ]
}

Query an Array of Embedded Documents, Specify a Query Condition on a Field Embedded in an Array of Documents¶. If you do not know the index position of the document nested in the array, concatenate  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 9 '19 at 17:49

Query on Embedded/Nested Documents, MongoDB Manual: How to query on embedded documents/nested documents/​subdocuments/nested fields. Query/select by embedded documents/nested  As of the 2.4.2 release of the C# drivers, the IFindFluent interface can be used for querying on array element. ElemMatch cannot be used on an array of strings directly, whereas the find interface will work on either simple or complex types (e.g. 'Tags.Name') and is strongly typed.

Query an Array, How do I find an element in an array in MongoDB? To query if the array field contains at least one element with the specified value, use the filter { <field>: <value> } where <value> is the element value. To query if the array field contains at least one element with the specified value, use the filter eq ( <field>, <value>) where value is the element value.

How can I check whether a field exists or not in MongoDB?, How do you check if a field exist in MongoDB? where I select a document which matches the title and which contains a document entry within its 'places' array that has name equal to 'someName'. However the issue is that the entries within the places array are large documents, and I only need a couple of fields from that document. I tried projecting the fields like so but it did not work.

Comments
  • Please try to update the link for the tutorial, its not correct.
  • I apologize and its updated
  • That link is not a tutorial and the problems stem from the data model. While you can embed data you would have to look up in a RDBMS, it is only reasonable to do so in OneTo(Very)Few™ relations. If I find the time later this day, I would come up with an alternate data model and a solution for the use case. What are the uids for? You already have an _id.
  • THe uid is the users _id. So in another collection I store user data and this is there "contacts" from their phone book.
  • So, there is no semantic difference, as you could use the _id for this as well. Gotcha.
  • Thank you for tips, I will implement this method. But how can I solve my question with it? I am very confused on how to implement this with my question
  • @Jagr Long story short: you need to get back to the drawing board and start over. Then, the problem you describe in your question does not even occur.
  • I tried it and it just provides one array value and I know there is 4 values
  • This is because $elemMatch returns ONLY the first matching item, not all matching items.
  • OHH! What can I use to return all?
  • Would I have to use the $in operator as well?
  • If you want to return all, you need to use aggregation with $filter. Check here: stackoverflow.com/questions/3985214/…