简体   繁体   中英

MongoDB with Mongoose - Find only certain child documents

MongoDB 3.0.7 and Mongoose 4.3.4.

Schema:

var schema = new mongoose.Schema({
    confirmed: { type: Boolean, default: false },
    moves: [new mongoose.Schema({
        name: { type: String, default: '' },
        live: { type: Boolean, default: true }
    })]
});
mongoose.model('Batches', schema);

Query:

var Batch = mongoose.model('Batches');
var query = {
    confirmed: true,
    moves: {
        $elemMatch: {
            live: true
        }
    }
};
Batch.find(query).exec(function(err, batches){
    console.log('batches: ', batches);
});

I need to return all batches that are confirmed , and all moves within the returned batches that are live .

At the moment, the above is returning only the confirmed batches (which is what I want), but all the moves in each returned batch (which is not what I want). So the limiting of moves by the live flag is not working.

How do I limit the sub-documents that are returned..?

Ideally, I would like to keep everything that controls the data returned within query passed to find , and not have to call more methods on Batch .

For Mongoose versions >=4.3.0 which support MongoDB Server 3.2.x , you can use the $filter operator with the aggregation framework to limit/select the subset of the moves array to return based on the specified condition. This returns an array with only those elements that match the condition, so you will use it in the $project stage to modify the moves array based on the filter above.

The following example shows how you can go about this:

var Batch = mongoose.model('Batches'),
    pipeline = [
        {
            "$match": { "confirmed": true, "moves.live": true }
        },
        { 
            "$project": {
                "confirmed": 1,
                "moves": {
                    "$filter": {
                         "input": "$moves",
                         "as": "el",
                         "cond": { "$eq": [ "$$el.live", true ] }
                     }
                }
            }
        }
    ];

Batch.aggregate(pipeline).exec(function(err, batches){
    console.log('batches: ', batches);
});

or with the fluent aggregate() API pipeline builder:

Batch.aggregate()
     .match({
         "$match": { "confirmed": true, "moves.live": true }
     })
     .project({
         "confirmed": 1,
         "moves": {
             "$filter": {
                  "input": "$moves",
                  "as": "el",
                  "cond": { "$eq": [ "$$el.live", true ] }
             }
         }
     })
     .exec(function(err, batches){
        console.log('batches: ', batches);
     });

For Mongoose versions ~3.8.8, ~3.8.22, 4.x which support MongoDB Server >=2.6.x , you could filter out the false values using a combination of the $map and $setDifference operators:

var Batch = mongoose.model('Batches'),
    pipeline = [
        {
            "$match": { "confirmed": true, "moves.live": true }
        },
        { 
            "$project": {
                "confirmed": 1,
                "moves": {
                    "$setDifference": [
                         {
                             "$map": {
                                 "input": "$moves",
                                 "as": "el",
                                 "in": {
                                     "$cond": [
                                         { "$eq": [ "$$el.live", true ] },
                                         "$$el",
                                         false
                                     ]
                                 }
                             }
                         },
                         [false]
                     ]
                 }
            }
        }
    ];

Batch.aggregate(pipeline).exec(function(err, batches){
    console.log('batches: ', batches);
});

You can use the aggregation framework's $unwind method to split them into separate documents, here are sample codes.

Batches.aggregate(
    { $match: {'confirmed': true, 'moves.live': true}}, 
    {$unwind: '$moves'},
    {$project: {
            confirmed: 1
            name: '$moves.name',
            live:'$moves.live'
        }
    }, function(err, ret){
})

The query does not limit moves by the live flag. The query reads: find all confirmed batches with at least one live move .

There are 2 options to retrieve live moves only: retrieve all moves, and filter the array clientside; or map-reduce it serverside - unwind all moves, filter live ones, and group by document id.

The former is simpler to implement, but will result with more data transfer, cpu and memory consumption on the client side. The later is more efficient, but a bit more complex to implement - if you expect more than 16Mb in the response, you will need to use a temporary collection.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM