Запрос вложенного документа с помощью mongoose

#javascript #node.js #mongodb #mongoose #aggregation-framework

#javascript #node.js #mongodb #mongoose #агрегация-фреймворк

Вопрос:

Я знаю, что этот вопрос задавали много раз, но я тоже новичок в 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")
}
  

Во-первых, мой вопрос: как я могу отфильтровать вложенный документ resumo по quesito? Возможно ли разбить этот результат на страницы, поскольку этот массив будет увеличиваться. И последний вопрос, хороший ли это подход к этому делу?

Спасибо, ребята!

Комментарии:

1. Разве это уже не возвращается так? Похоже, вы смешали здесь как встроенные, так и ссылочные проекты. Хотя вы предоставляете ссылку на внешний объект, вы также, похоже, определили содержащиеся в нем поля как во встроенной структуре. Как вы на самом деле сохраняете свои данные?

Ответ №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 путем «фильтрации» внутри a $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 and $limit here действительно имеет смысл только тогда, когда вы обрабатываете один документ и просто «фильтруете» и «просматриваете» массив. Это можно сделать с несколькими документами, но это очень сложно, поскольку нет способа просто «нарезать» массив. Что подводит нас к следующему пункту.

Действительно, со встроенными массивами для подкачки, которая не требует никакой фильтрации, вы просто используете $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. Спасибо, чувак, это сработало, но я решил изменить свою схему, чтобы было лучше и быстрее запрашивать!

Ответ №2:

Я не уверен, что вы можете фильтровать вложенный документ напрямую с помощью mongoose. Однако вы можете получить родительский документ с Model.find({'resumo.quesito': 'THEVALUE'}) помощью (вы также должны указать на него индекс)

Затем, когда у вас есть родительский элемент, вы можете получить дочерний элемент, сравнив quesito

Дополнительный документ можно найти здесь: http://mongoosejs.com/docs/subdocs.html

Комментарии:

1. Я не понимаю, как это было полезно, я уже знаю это, и я спросил, используя mongoose. Спасибо!