使用mongoose查询嵌套文档

时间:2014-06-15 06:10:29

标签: javascript node.js mongodb mongoose aggregation-framework

我知道很多次都会问过这个问题,但我对mongo和mongoose也有点新意,我无法理解!

我的问题:

我有一个看起来像这样:

var rankingSchema = new Schema({
    userId : { type : Schema.Types.ObjectId, ref:'User' },
    pontos : {type: Number, default:0},
    placarExato : {type: Number, default:0},
    golVencedor : {type: Number, default:0},
    golPerdedor : {type: Number, default:0},
    diferencaVencPerd : {type: Number, default:0},
    empateNaoExato : {type: Number, default:0},
    timeVencedor : {type: Number, default:0},
    resumo : [{
        partida : { type : Schema.Types.ObjectId, ref:'Partida' },
        palpite : [Number],
        quesito : String
    }]
});

哪会返回这样的文件:

{
    "_id" : ObjectId("539d0756f0ccd69ac5dd61fa"),
    "diferencaVencPerd" : 0,
    "empateNaoExato" : 0,
    "golPerdedor" : 0,
    "golVencedor" : 1,
    "placarExato" : 2,
    "pontos" : 78,
    "resumo" : [ 
        {
            "partida" : ObjectId("5387d991d69197902ae27586"),
            "_id" : ObjectId("539d07eb06b1e60000c19c18"),
            "palpite" : [ 
                2, 
                0
            ]
        }, 
        {
            "partida" : ObjectId("5387da7b27f54fb425502918"),
            "quesito" : "golsVencedor",
            "_id" : ObjectId("539d07eb06b1e60000c19c1a"),
            "palpite" : [ 
                3, 
                0
            ]
        }, 
        {
            "partida" : ObjectId("5387dc012752ff402a0a7882"),
            "quesito" : "timeVencedor",
            "_id" : ObjectId("539d07eb06b1e60000c19c1c"),
            "palpite" : [ 
                2, 
                1
            ]
        }, 
        {
            "partida" : ObjectId("5387dc112752ff402a0a7883"),
            "_id" : ObjectId("539d07eb06b1e60000c19c1e"),
            "palpite" : [ 
                1, 
                1
            ]
        }, 
        {
            "partida" : ObjectId("53880ea52752ff402a0a7886"),
            "quesito" : "placarExato",
            "_id" : ObjectId("539d07eb06b1e60000c19c20"),
            "palpite" : [ 
                1, 
                2
            ]
        }, 
        {
            "partida" : ObjectId("53880eae2752ff402a0a7887"),
            "quesito" : "placarExato",
            "_id" : ObjectId("539d0aa82fb219000054c84f"),
            "palpite" : [ 
                2, 
                1
            ]
        }
    ],
    "timeVencedor" : 1,
    "userId" : ObjectId("539b2f2930de100000d7356c")
}

我的问题是,首先:如何通过q​​uesito过滤resumo嵌套文档?是否可以对此结果进行分页,因为此数组将会增加。最后一个问题,对于这种情况,这是一个很好的方法吗?

谢谢你们!

2 个答案:

答案 0 :(得分:5)

如上所述,即使您正在存储外部引用,您的架构也意味着您实际上已经嵌入了数据。因此,目前尚不清楚您是同时进行嵌入和引用还是仅仅嵌入。

这里最大的警告是匹配“文档”和实际过滤数组内容之间的区别。既然你似乎在谈论“分页”你的数组结果,那么这里的重点是做到这一点,但仍然提到警告。

数组中的多个“已过滤”匹配需要聚合框架。您通常可以“投射”数组元素的单个匹配项,但是如果您需要多个匹配项,则需要这样做:

Ranking.aggregate(
    [ 
        // This match finds "documents" that "contain" the match
        { "$match": { "resumo.quesito": "value" } },

        // Unwind de-normalizes arrays as documents
        { "$unwind": "$resumo" },

        // This match actually filters those document matches
        { "$match": { "resumo.quesito": "value" } },

        // Skip and limit for paging, which really only makes sense on single
        // document matches
        { "$skip": 0 },
        { "$limit": 2 },

        // Return as an array in the original document if you really want
        { "$group": {
            "_id": "$_id",
            "otherField": { "$first": "$otherField" },
            "resumo": { "$push": "$resumo" }
        }}
    ],
    function(err,results) {


    }
)

使用$project运算符在$map内“过滤”MongoDB 2.6。但是你仍然需要$unwind来“寻找”数组位置,但是由于数组首先被“过滤”,所以可能会有更少的处理:

Ranking.aggregate(
    [ 
        // This match finds "documents" that "contain" the match
        { "$match": { "resumo.quesito": "value" } },

        // Filter with $map
        { "$project": {
              "otherField": 1,
              "resumo": {
                  "$setDifference": [
                      {
                          "$map": {
                              "input": "$resumo",
                              "as": "el",
                              "in": { "$eq": ["$$el.questio", "value" ] }
                          }
                      },
                      [false]
                  ]
              }
        }},          

        // Unwind de-normalizes arrays as documents
        { "$unwind": "$resumo" },

        // Skip and limit for paging, which really only makes sense on single
        // document matches
        { "$skip": 0 },
        { "$limit": 2 },

        // Return as an array in the original document if you really want
        { "$group": {
            "_id": "$_id",
            "otherField": { "$first": "$otherField" },
            "resumo": { "$push": "$resumo" }
        }}
    ],
    function(err,results) {


    }
)

此处$skip$limit的内部用法在您处理单个文档并仅“过滤”和“分页”数组时才有意义。可以使用多个文档执行此操作,但由于无法“切片”数组,因此非常复杂。这将我们带到下一点。

对于嵌入式阵列,对于不需要任何过滤的分页,您只需使用专为此目的而设计的$slice运算符:

Ranking.find({},{ "resumo": { "$slice": [0,2] } },function(err,docs) {

});

您的备用方法是简单地引用外部集合中的文档,然后将参数传递给mongoose .populate()以过滤和“分页”结果。架构本身的变化就是:

    "resumo": [{ "type": "Schema.Types.ObjectId", "ref": "Partida" }]

外部引用的集合现在保存对象详细信息而不是直接嵌入到数组中。 .populate()与过滤和分页的使用是:

 Ranking.find().populate({
     "path": "resumo",
     "match": { "questio": "value" },
     "options": { "skip": 0, "limit": 2 }
 }).exec(function(err,docs) {

     docs = docs.filter(function(doc) {
         return docs.comments.length;   
     });
 });

当然可能存在的问题是,您现在无法再查询包含“嵌入”信息的文档,因为它现在位于另一个集合中。这导致拉入所有文档,尽管可能通过其他一些查询条件,但随后手动测试它们以查看它们是否被发送以检索这些项目的筛选查询“填充”。

所以它确实取决于你在做什么以及你的方法是什么。如果您经常打算在内部数组上“搜索”,那么嵌入通常会更适合您。此外,如果您真的只对“分页”感兴趣,那么$slice运算符可以很好地用于嵌入式文档。但要注意增长嵌入式阵列太大。

使用带有mongoose的引用模式有助于解决某些问题,并且有一些方法可以帮助“分页”结果并过滤它们。缺点是你不能再从父本身那里查询那些元素。因此,内部元素的父选择不适合这里。另请注意,虽然并非所有数据都是嵌入的,但仍然可以引用外部文档的_id值。所以你仍然可以得到大型阵列,这可能是不可取的。

对于任何大的东西,考虑一下你可能会自己做这项工作,然后从“孩子”项目向后工作,然后匹配父母。

答案 1 :(得分:-3)

我不确定您是否可以使用mongoose直接过滤子文档。但是,您可以使用Model.find({'resumo.quesito': 'THEVALUE'})获取父文档(您还应该使用索引)

然后当你有父母时,你可以通过比较quesito

来得到孩子

可以在此处找到附加文档:http://mongoosejs.com/docs/subdocs.html