javascript - 使用 mongoose 查询嵌套文档

标签 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")
}

我的问题是,首先:如何通过 quesito 过滤简历嵌套文档?是否可以对这个结果进行分页,因为这个数组将会增加。最后一个问题,对于这个案例来说,这是一个好的方法吗?

谢谢大家!

最佳答案

如上所述,您的架构意味着您实际上拥有嵌入的数据,即使您正在存储外部引用。因此,不清楚您是否同时进行嵌入和引用,或者只是自行嵌入。

这里需要注意的是匹配“文档”和实际过滤数组内容之间的区别。由于您似乎在谈论“分页”数组结果,因此这里的重点是这样做,但仍然提到警告。

数组中的多个“过滤”匹配需要聚合框架。通常,您可以“投影”数组元素的单个匹配项,但在您期望多个元素的情况下需要这样做:

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


    }
)

或者 MongoDB 2.6 方式,通过在 $project 中“过滤”使用$map运算符(operator)。但你仍然需要$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 值的引用。因此,您仍然可能会得到大型数组,这可能是不可取的。

对于任何大型项目,请考虑您可能会自己完成这项工作,并从“子”项开始向后工作,然后匹配父项。

关于javascript - 使用 mongoose 查询嵌套文档,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24227047/

相关文章:

mongodb - Morphia/MongoDB : ordering search results from advanced queries

javascript - for循环中的时间比较

javascript - JQUERY - 自调整导航

javascript - 为什么我无法收到删除请求的请求查询?

linux - 我如何从 shell 启动 2 个线程?

javascript - Bookshelf.js - 如何仅获取一个查询的 'hidden' 字段?

javascript - 更改为 Bluebird Promise,但使用 mongoose 仍然收到弃用警告,为什么?

ruby-on-rails - Mongoid 有 Map/Reduce 吗?

javascript - Nivo-slider 似乎在 header div 中放置了一个内联 css 高度

javascript - AngularJS ui-router不加载模板