MongoDB: Updating a sub properties inside a nested array document

818 views Asked by At

I have a structure which looks like below:

course: { // course 
   id,
   coursename,
   sections: { // has an array of sections
    section: {
      id,
      sectionname
      cards: {  // each section has an array of cards
         card: {
            id  
            cardname 
         }
      }
    }
  }
}

Now, I am given the card ID. Suppose cardID is 301. I don't have details about the course or the section in which the card is stored. I only have the cardID. I want to update the cardname from "Test 1" to "Test 2".

Please provide some information about how to update the name of card given only the cardId. Also please provide information about how I can access a single card using only cardId.

If i run the following query:

 db.course.find( {"sections.cards.id" : ObjectId("5859517447c4286d0f0639ee")},
{"sections.cards.$":1,_id:0})

The whole section object is returned instead of returning a single card. Please provide some infromation about how to access a single card using only cardId.

An example of document that i have:

{ 
   "_id" : ObjectId("58595041cfdc46100f92a985"), 
   "modelType" : "Course",
   "name" : "c1",
   "sections" : [ 
        { 
           "id" : ObjectId("5859504dcfdc46100f92a986"),
           "name" : "s1", 
           "cards" : [ 
               { 
                  "id" : ObjectId("58595057cfdc46100f92a987"),
                  "name" : "card1", 
               }, 
               { 
                  "id" : ObjectId("5859506ccfdc46100f92a988"),
                  "name" : "card2"
               }
         ]
      }
   ] 
}

Please provide information about how to update a card using only card Id.

Suppose I want to change the card's name. How do I achieve it?

1

There are 1 answers

2
DennyHiu On BEST ANSWER

I hope this answer is enough for your needs:

db.collection.aggregate([
   {$unwind: "$sections"}, // unwind the array
   {$unwind: "$sections.cards"}, // unwind the array
   {$match: {"sections.cards.id": ObjectId("5859506ccfdc46100f92a988")}}, // filter
   {$project: {_id: 0, id: "$sections.cards.id", name: "$sections.cards.name"}} // cut and clear any property that you don't need
])

which result in(I tested it using your sample):

{
   "name" : "card2",
   "id" : ObjectId("5859506ccfdc46100f92a988")
}

Explanation:

  1. First off, you need aggregate(find any library like mongojs or mongoose and how to do this within their respective manual),
  2. then unwind sections and cards inside it.
  3. After both steps, you need to filter documents resulted from unwind operation according to your requirement. In this case: which card match your ID
  4. Clear any properties that you don't need
  5. Return the result

To Update properties inside a document:

It's impossible to update an element inside of an array without knowledge of its index, so first, you must find out its index/position inside. The simplest way I could think of is by using for (since yours is double tier-ed array, I think mapReduce method will be much more complicated):

// first, find the document, where xxx is the target card ID
db.collection.find({'sections.cards.id': ObjectId("xxx")}, function(err, reply){
     // iterates through the documents
     for(var z = 0; z < reply.length; z++){
         // iterates through the `sections`
         for(var y = 0; y < reply[z].sections.length; y++){
             // iterates through the `cards`
             for(var x = 0; x < reply[z].sections[y].cards.length; x++){
                  if(reply[z].sections[y].cards[x].id.toString() === "xxx")                   
                  {
                       // do what you want to do the card here
                       reply[z].sections[y].cards[x].name = "updated name";
                  }
             }
         }

         // save the updated doc one-by-one,
         db.collection.update({_id: reply._id}, reply);
     }


});

The best solution

The best solution is: Don't have this kind of document in the first place. schema-free database approach DOESN'T mean that you can put everything inside a document. You had to consider the functionality and accessibility of data inside next time you design the document schema.

You can rationalize the documents according to your needs. In this case, you should split the course document, since it clear that card property should be an independent collection from course. If you worry that you'll need to join both collection, worry not. Since 3.2 mongodb introduced $lookup for this kind of application.

Not only it'll make your life easier, it'll make mongo queries run faster too.