用猫鼬查询嵌套文档

Query nested document with mongoose

本文关键字:嵌套 文档 查询      更新时间:2023-09-26

我知道这个问题已经被问过很多次了,但我对猫鼬和猫鼬也不太熟悉,所以我想不出来!

我问题:

我有一个像这样的:

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

我的问题是,首先:我如何通过问卷过滤简历嵌套文档?是否有可能对这个结果分页,因为这个数组将会增加。最后一个问题,这是处理这种情况的好方法吗?

如前所述,您的模式意味着您实际上已经嵌入了数据,即使您正在存储外部引用。因此,不清楚您是在做嵌入和引用还是简单地嵌入本身。

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

数组中的多个"过滤"匹配需要聚合框架。您通常可以"投影"数组元素的单个匹配项,但在您希望有多个匹配项时需要这样做:

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 操作符。但是仍然需要 $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 的内部用法只有在处理单个文档并且只是"过滤"answers"分页"数组时才有意义。可以对多个文档执行此操作,但是非常复杂,因为没有办法只"切片"数组。这就引出了下一个问题。

对于嵌入式数组,对于不需要任何过滤的分页,您只需使用 $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值的引用。因此,您仍然可以使用大数组,这可能不是理想的。

对于任何大的元素,请考虑您可能自己完成工作,并从"子"项向后工作,然后匹配父项。

我不确定您是否可以直接使用mongoose过滤子文档。然而,你可以用Model.find({'resumo.quesito': 'THEVALUE'})获得父文档(你也应该在它上面有一个索引)

当你有了父母你可以通过比较问卷得到孩子

更多文档可以在这里找到:http://mongoosejs.com/docs/subdocs.html