使用 mongoose 查询嵌套文档

Posted

技术标签:

【中文标题】使用 mongoose 查询嵌套文档【英文标题】:Query nested document with mongoose 【发布时间】:2014-08-05 07:05:48 【问题描述】:

我知道这个问题已经被问过很多次了,但我对 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 过滤简历嵌套文档?是否可以对这个结果进行分页,因为这个数组会增加。最后一个问题,这是处理这种情况的好方法吗?

谢谢你们!

【问题讨论】:

这不是已经返回了吗?您似乎在这里混合了嵌入式和参考设计。虽然您确实提供了外部对象引用,但您似乎也将包含的字段定义为嵌入式结构。您实际上是如何保存数据的? 【参考方案1】:

如前所述,您的架构意味着您实际上已经嵌入了数据,即使您存储的是外部引用。所以不清楚你是同时做嵌入和引用还是仅仅自己嵌入。

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

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

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 方式,通过使用 $map 运算符在 $project 内“过滤”。但是您仍然需要$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 值的引用。所以你仍然可以得到大型数组,这可能是不可取的。

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

【讨论】:

谢谢,这行得通,但我决定更改我的架构,这样查询会更好更快!【参考方案2】:

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

然后当你有父母你可以通过比较quesito得到孩子

可在此处找到其他文档:http://mongoosejs.com/docs/subdocs.html

【讨论】:

我不明白这有什么用,我已经知道了,我问过使用猫鼬。谢谢!

以上是关于使用 mongoose 查询嵌套文档的主要内容,如果未能解决你的问题,请参考以下文章

Mongoose - 使用 find() 查询文档,其中嵌套数组值的总和在一定范围内

Mongoose更新嵌套数组

Mongoose 查询返回带有空数组的嵌套文档

Mongoose - 查询深度嵌套的对象

文档不会使用带有 mongoose 的 save() 方法保存

MongoDB Mongoose 聚合查询深度嵌套数组删除空结果并填充引用