Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use mongoose query to get only a subdocument

I have a similar datastructure as this:

var GrandGrandChild = mongoose.Schema({
    attribute: String,
    id: Number
});

var GrandChild = mongoose.Schema({
    children: [GrandGrandChild],
    id: Number,
    irrelevantAttribute: String
});

var Child = mongoose.Schema({
    children: [GrandChild],
    id: Number,
    irrelevantAttribute2: String
});

var Parent = mongoose.Schema({
    children: [Child],
    id: Number,
    irrelevantAttribute3: String
});

var GrandParent = mongoose.Schema({
    children: [Parent],
    id: Number,
    irrelevantAttribute4: String
});

These are a lot of collections with subdocuments in them. Note that the ID's are unique to their siblings, but not unique to all elements with that same schema.

So one grand parent can have an parent with id 0, and another grandparent can also have a parent with id 0. but one grandparent can not have 2 parents with id 0.

The only schema that gets saved is the GrandParent schema, and mongoose/mongodb makes a nice big single document of all the data of this grandparent. (Exactly what i am looking for)

So here is my issue: I have a GrandParent ID, Parent ID, Child ID, GrandChildID and GrandGrandChild ID, and i want to somehow get only the GrandGrandChild object to which all these ID's are pointing.

The ugly way would be to, but currently the only way i can get to work, is to make a query that gets this big document of GrandParent, and manually loop through all arrays to find the right Parent, then loop again to find the right child, then loop again to find the right grandchild, then loop again and find the grandgrandchild im needing here.

My question is, how would i compose a query in mongoose that either returns only the grandgrandchild document, or the grandparent document with only the children attribute included, and in that children attribute only the parent object included that refers to the child object that refers to the grandchild object that refers to the grandgrandchild object, allowing the following with the result:

GRANDPARENT  PARENT      CHILD      GRANDCHILD   GRANDGRANDCHILD
grandparent.children[0].children[0].children[0].children[0].attribute;

I hope someone can help me on this query, as far is i got is this:

GrandParentModel.findOne(
    {
        "id" : 0,
        "children.id" : 0,
        "children.children.id" : 0,
        "children.children.children.id" : 0,
        "children.children.children.children.id" : 0
    },
    {"children.children.children.children.$" : 1}, callback);

The problem with this query is that the unnessicary siblings arent trimmed away.

I hope someone can help me out.

Hylke Bron

like image 970
Hylke Avatar asked Apr 23 '13 09:04

Hylke


2 Answers

it has been some time since I asked this question, but I think I found a rather elegant way of working with these kind of structures.

In this case I'll show how it works with only GrandParent, Parent and Child.

Instead of storing a list of subdocuments in each document (GrandParent.children, Parent.children), I created an unique identifier of the following structure:

Child.referenceId = {
    grandparent: "some key to the grandparent of the parent",
    parent: "some key to the parent",
    child: "key of this child"
};

Parent.referenceId = {
    grandparent: "some key to its grandparent",
    parent: "key of this parent"
}

GrandParent.referenceId = {
    grandparent: "key of this parent"
}

This creates a hierarchy of GrandParent > Parent > Child.

The models would be something like the following:

var idStructure = {
    grandparent: { type: String, required: true },
    parent: { type: String, required: false },
    child: { type: String, required: false }
};

var GrandParent = mongoose.Schema({
    id: idStructure,
    irrelevantAttribute: String
});

var Parent = mongoose.Schema({
    id: idSructure,
    irrelevantAttribute: String
});

var Child = mongoose.Schema({
    id: idStructure,
    irrelevantAttribute: String
});

Notice that a Parent doesnt directly knows its parent, for they are not stored as subdocuments. Yet there still is a connection between Parent and Child, through the referenceId.

When searching for the whole familytree of a GrandParent, one would simply execute 3 queries, and then connect them correctly:

// First find all children which belong to the grandparent
Child.find({"id.grandparent" : "some key to the grandparent"})
.exec(function(err, children)
{
     if(err)
         return;

     Parent.find({"id.grandparent" : "some key to the grandparent"})
     .exec(function(err, parents)
     {
         if(err)
             return;

         // Loop through the parents and children to connect them before returning to a client
         for(var i = 0; i < parents.length; i++)
         {
             var parent = parents[i];
             parent.children = [];
             // loop through the children to check if they belong to the current parent
             for(var j = 0; j < children.length; j++)
             {
                 var child = children[j];
                 if(parent.id.parent == child.id.parent)
                     parent.children.push(child);
             }
         }

         // After filling the children into the parents, get the grandparents and do the same for the parents and grandparents as done for the children and parents.
        GrandParent.find({"id.grandparent" : "some key to the grandparent"})
       .exec(function(err, grandparents)
       {
           // TODO: the same as done above (two loops, one loops the grandparents, other loops the parents
           // Once this is finished, we have a filled grandparent
       });

     });
});

The code above would result in just ONE grandparent, filled with parents, which are filled with children.

The reason no more grandparents would be found is because the id of the grandParent should be unique, for the referenceId of the grandparent only has a grandparent property.

I hope i made my point clear, because through this method, one can easily search for one specific child, easily get its parent through the reference id, and its grandparent also through the reference id.

It might be a bit complex, but once you figure the method out for yourself, its all kinda straight forward.

Hylke

like image 191
Hylke Avatar answered Sep 28 '22 05:09

Hylke


Is very difficult to get this kind of things work in a clean way.

I didn't find a clean solution on this topic, but maybe I can help you with the looping thing. You can avoid the loop using: var doc = parent.children.id(id); Finding a sub-document

I hope this help you. Regards, Sebastian.

like image 33
Sebastian Bromberg Avatar answered Sep 28 '22 07:09

Sebastian Bromberg