使用Mongoose查询仅获取子文档。

8

我有一个类似于这样的数据结构:

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

这些集合中包含许多子文档。请注意,ID对于其同级元素是唯一的,但对于具有相同模式的所有元素而言并不唯一。
因此,一个祖先可以拥有一个ID为0的父代,另一个祖先也可以拥有一个ID为0的父代。但是,一个祖先不能拥有2个ID为0的父代。
唯一保存的架构是GrandParent架构,mongoose/mongodb将这个祖先的所有数据制成一个很好的大型单一文档。(正是我正在寻找的)
所以问题来了:我有一个GrandParent ID、Parent ID、Child ID、GrandChildID和GrandGrandChild ID,我想以某种方式仅获取所有这些ID指向的GrandGrandChild对象。
丑陋的方法是,但目前我能够使之工作的唯一方法是,创建一个查询来获取这个GrandParent的大型文档,并手动循环遍历所有数组以找到正确的Parent,然后再次循环以找到正确的child,然后再次循环以找到正确的grandchild,最后再次循环并找到我需要的grandgrandchild。
我的问题是,我该如何在mongoose中组合一个查询,它要么只返回grandgrandchild文档,要么返回只包含children属性的grandparent文档,其中,在该children属性中仅包含引用到grandgrandchild对象的grandchild对象、引用到该grandchild对象的child对象和引用到该child对象的parent对象,从而实现以下结果:
GRANDPARENT  PARENT      CHILD      GRANDCHILD   GRANDGRANDCHILD
grandparent.children[0].children[0].children[0].children[0].attribute;

我希望有人能够帮助我解决这个问题,目前我所知道的是:

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

这个查询的问题在于没有削减掉不必要的兄弟节点。
希望有人能帮我一下。
Hylke Bron
3个回答

3

我知道这个问题已经有一段时间了,但我认为我找到了一种比较优雅的方法来处理这些结构。

在这种情况下,我将展示如何仅使用GrandParent、Parent和Child来实现它的工作方式。

与其在每个文档中存储子文档列表(GrandParent.children、Parent.children),我创建了以下唯一标识符的结构:

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

这将创建一个祖父级(GrandParent)> 父级(Parent)> 子级(Child)的层次结构。

模型应该像以下这样:

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

请注意,父文档并不直接知道它的父文档,因为它们不作为子文档存储。然而,通过引用ID,父文档和子文档之间仍然存在连接关系。
当搜索祖先的整个家族谱时,只需执行3个查询,然后正确连接它们。
// 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
       });

     });
});

上面的代码会得到一个只有一个祖父,里面填满了父母,他们又填满了孩子。
之所以没有找到更多的祖父是因为祖父的id应该是唯一的,因为祖父的referenceId只有一个祖父属性。
我希望我的观点清晰明了,因为通过这种方法,人们可以轻松地搜索一个特定的孩子,通过引用id轻松获取它的父母和祖父母。
可能有点复杂,但一旦你自己理解了这个方法,就都很简单明了。
Hylke

一些相关的YouTube视频,其中一个是关于“最小基数原则”的(https://www.youtube.com/watch?v=L994ZiVuSTE),另一个是关于[存储分层数据](https://www.youtube.com/watch?v=T5Yt6Ndm2QY)的示例。这来自于EDX上一个与Mongoose相关的课程,名为“使用MEAN堆栈介绍MongoDB”(https://courses.edx.org/courses/course-v1:MongoDBx+M101x+3T2015/info)。 - Adriano

2

在清晰的方式下让这类事情起作用非常困难。

我没有找到关于这个主题的清晰解决方案,但也许我可以帮你解决循环问题。 您可以使用以下方法避免循环: var doc = parent.children.id(id); 查找子文档

希望这可以帮助您。 问候, 塞巴斯蒂安。


但在这种情况下,我首先得到一个大文档,然后从中选择我需要的所有字段。相反,我只获取一个小的子文档。我认为处理小文档比处理大文档更快。 - Anton Shuvalov

1
这对我起作用了。
      model.find({_id:args.id},{ commentList: { $elemMatch: { _id: todo.commentList[todo.commentList.length-1] } } },(err, todos) => {
         if (err) reject(err)
         else resolve(todos)
         console.log(todos);
      })

{{link1:$elemMatch(投影)}}


(保留原文,不做翻译)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接