简体   繁体   中英

Find documents by array of embedded references in Moongoose (MongoDB)

Assuming I have the following collection schema

var terminalSchema = new mongoose.Schema({
    name: 'String', 
    ip: 'String'
});

var wayPointSchema = new mongoose.Schema({
    name: 'String',
    description: 'String',
    long: 'Number',
    lat: 'Number',
    distances: [{
        toTerminal : { type: Schema.Types.ObjectId, ref: 'Terminal' },
        km : 'Number',
        minutes: 'Number'
    }]
});

How can I find all WayPoints with terminal.ip = '10.0.0.1'

I have tried the following with no luck...

WayPoint.find()
.populate({
    path: 'distances.toTerminal',
    match:  { 'distances.toTerminal.ip': '10.0.0.1'},
})
.exec(function(err, wp) {
    if(err || !wp) {
          throw err;
        } else {
          console.log(wp);
        }
})

The entire collection is returned

Update - Progress?

I think I've made some progress with the following code, as it now only shows [object] for matching sub-documents and null for sub-documents that don't match.

WayPoint.find({'distances.toTerminal': {$exists: true}})
.populate({
    path: 'distances.toTerminal',
    select: 'description',
    match:  { ip: '10.0.0.1'}
})
.exec(function(err, wp) {
    if(err || !wp) {
          throw err;
        } else {
          console.log(wp);
        }
})

The "match" part of .populate() here is not actually the "full" visualized path from the "WayPoint" object but actually only applies to the "Terminal" object. The "WayPoint" only has the reference so you do this:

WayPoint.find()
    .populate({
        "path": "distances.toTerminal",
        "match": { "ip": "10.0.0.1" }
    })
    .exec(function(err,wp) {
        if (err) throw err;  // or otherwise handle

        wp = wp.filter(function(doc) {
            doc.distances = doc.distances.filter(function(distance) {
                return distance.toTerminal != null;
            });
            return doc.distances.length > 0;
        });

        console.dir(wp);
    });

That really isn't a very efficient way to find a "WayPoint" by the "Terminal" ip value as mongoose actually fetches all "WayPoint" items and you have to "filter" yourself in order to find the ones that matched.

The better way would be to "embed" the document, then you can issue the top level query to find only documents that match:

WayPoint.find({ "distances.toTerminal.ip": "10.0.0.1" },function(err,wp) {

That is the general MongoDB way, but if you cannot do that or it is otherwise impractical, then you would be better of finding the _id of the matching "Terminal" object or objects and passing that to your "WayPoint" query. A little help from the "async" library to clean up the code:

async.waterfall(
    [
        function(callback) {
            Terminal.find({ "ip": "10.0.0.1" },function(err,terms) {
                if (err) throw err;
                ids = terms.map(function(term) { return term._id; });
                callback(err,ids);
            });
        },

        function(ids,callback) {
            WayPoint.find(
               { "distances.toTerminal": { "$in": ids } },
               function(err,wp) {
                   if (err) throw err;
                   console.dir( wp );
                   callback();
               }
            );
        }
    ]
);

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