Multiple queries inside mongodb query

2019-08-05 18:25发布

问题:

I'm having an issue when trying to query based on the result of another query on mongodb.

I'm trying to make an initial query and then do another query for each one of the result of the first query. The reason I'm doing it like this is because I have two different collections and I need to join some data from one collection with the data of the other collection. In a SQL world I could easily do this with a JOIN, but as I'm using mongodb in this one I can't really use JOINs, so I guessed doing a for loop inside the first query's callback function would be the way to go.

Here's the code I'm using...

var resultSet = [];
db.get('busstopcollection').find({id_bus_stop: parseInt(req.body.busstopid)}, function(e, docs){
    if(e || docs.length === 0) {
        console.log("Sorry, wrong id.");
        return e;
    }
    for(var m=0; m<docs.length; m++){

        var auxRes = {};
        auxRes.id_bus = docs[m].id_bus;
        auxRes.id_bus_stop = docs[m].id_bus_stop;
        auxRes.coord_x = docs[m].coord_x;
        auxRes.coord_y = docs[m].coord_y;
        auxRes.id_bus_variation = docs[m].id_bus_variation;
        db.get('buscollection').find({id_bus: parseInt(docs[m].id_bus)}, function(e, busDocs){
            auxRes.s_origin_description = busDocs[0].s_origin_description;
            auxRes.s_destination_description = busDocs[0].id_destination_description;
            resultSet.push(auxRes);
        });
        res.send(JSON.stringify(resultSet));
    }
});

I need to res.send the resultSet array after all the values have been added.

I've tried some other ways of doing this, but the thing is that when the res.send line is reached the second query hasn't finished at all. I also tried doing that inside the inner query's callback, but I need to check if it's the last in the for loop, and checking the value o m won't do it as it always is equivalent to docs.length.

As far as I know there's no such thing as a synchronous query in mongodb, but maybe I'm wrong.

What's the right way of doing this?

EDIT

I found a way around it, but I'm sure there's got to be a better way. Here's how I'm doing it...

db.get('busstopcollection').find({id_bus_stop: parseInt(req.body.busstopid)}, function(e, docs){
    if(e || docs.length === 0) {
        console.log("Ha ocurrido un error, no existe esa parada");
        return e;
    }
    var busIDs = [];
    for(var m=0; m<docs.length; m++){
        busIDs.push(parseInt(docs[m].id_bus));
        var auxRes = {};
        auxRes.id_bus = docs[m].id_bus;
        auxRes.id_bus_stop = docs[m].id_bus_stop;
        auxRes.coord_x = docs[m].coord_x;
        auxRes.coord_y = docs[m].coord_y;
        auxRes.id_bus_variation = docs[m].id_bus_variation;
        resultSet.push(auxRes);
    }
    db.get('buscollection').find({id_bus: {$in: busIDs}}, function(e, busDocs){
        for(var n = 0; n<busDocs.length; n++){
            for(var k=0; k<resultSet.length; k++){
                if(resultSet[k].id_bus == busDocs[n].id_bus){
                    resultSet[k].s_origin_description = busDocs[n].s_origin_description;
                    resultSet[k].s_destination_description = busDocs[n].id_destination_description;
                }
            }
        }
        res.send(JSON.stringify(resultSet));
    });
});

回答1:

Your updated solution in your question is generally fine, as using $in is an excellent way of fetching a set of results (you'll want to make sure that you've indexed the id_bus property).

Here are a few tweaks (with a bit of cleanup and optimization):

db.get('busstopcollection')
  .find({id_bus_stop: parseInt(req.body.busstopid)}).toArray(function(e, docs){
    var auxById = {};   // store a dictionary of all the results for later
    if(e || docs === null || docs.length === 0) {
        console.log("Ha ocurrido un error, no existe esa parada");
        return e;
    }
    var busIDs = [];
    docs.forEach(function(doc) {
        busIDs.push(parseInt(doc.id_bus));
        // consider just using the doc directly rather than copying each property 
        // especially if you're not manipulating any of the data as it passes
        var auxRes = {        
            id_bus : doc.id_bus,
            id_bus_stop : doc.id_bus_stop,
            coord_x : doc.coord_x,
            coord_y : doc.coord_y,
            id_bus_variation : doc.id_bus_variation
        };
        // you could just use each doc directly ...
        // var auxRes = doc; ??
        // ** importantly, store off the id_bus for each one so you can
        // ** avoid a costly loop trying to match an id below.
        auxById[doc.id_bus] = auxRes;
        resultSet.push(auxRes);
    });
    // might want to consider using a cursor ... here's an example
    db.get('buscollection')
        .find({id_bus: {$in: busIDs}}).each(function(e, busDoc){
        // the last item in the cursor will be null
        if (busDoc === null) {
            res.send(JSON.stringify(resultSet)); 
            return;
        }
        var res = auxById[busDoc.id_bus];
        if (res) {   // did we find it in our dictionary of results?
           // yes, we did == copy the necessary field data
           res.s_origin_description = busDoc.s_origin_description;
           res.s_destination_description = busDoc.id_destination_description;
        }            
    });
});


回答2:

Node.js behavior is asynchronous , programmer has to code taking consideration of this behavior. Use callbacks or promises or a flow control library . In your your program , you have put mongo query inside loop , which is a bad approach of querying . Instead if querying multiple times , use $in operator . It will optimize your code performance and solves your response sending problem also.

var resultSet = [];
        db.get('busstopcollection').find({id_bus_stop: parseInt(req.body.busstopid)}, function(e, docs){
            if(e || docs.length === 0) {
                console.log("Sorry, wrong id.");
                return e;
            }
            var bus_ids = [];
            for(var m=0; m<docs.length; m++){
                var auxRes = {};
                auxRes.id_bus = docs[m].id_bus; 
                bus_ids.push(parseInt(docs[m].id_bus)); // collect all ids 
                auxRes.id_bus_stop = docs[m].id_bus_stop;
                auxRes.coord_x = docs[m].coord_x;
                auxRes.coord_y = docs[m].coord_y;               
                auxRes.id_bus_variation = docs[m].id_bus_variation;
                resultSet.push(auxRes);         

            }
            // Query at one time for all document 
            db.get('buscollection').find({id_bus: {$in : bus_ids}}).toArray( function(e, busDocs){
                    // Now find and merge in one go
                    busDocs.forEach(function(eachBusDoc){
                        for(var i=0,len = resultSet.length;i< len;i++){
                            if(resultSet[i].id_bus == busDocs.id_bus ){
                                resultSet[i].s_origin_description = eachBusDoc.s_origin_description;
                                resultSet[i].s_destination_description = eachBusDoc.id_destination_description;
                            }
                        }
                    });             
                       res.send(JSON.stringify(resultSet));
                });
        });