11

How can I run mongoose query in forEach loop in nodejs and get inner join result need of both collections?

like below details

userSchema.find({}, function(err, users) {
    if (err) throw err;
    users.forEach(function(u,i){
        var users = [];
        jobSchema.find({u_sno:s.u.sno}, function(err, j) {
            if (err) throw err;
            if (!u) {
                res.end(JSON.stringify({
                    status: 'failed:Auction not found.',
                    error_code: '404'
                }));
                console.log("User not found.");
                return 
            }
            users.push(j);
        })
    })
    res.send(JSON.stringify({status:"success",message:"successfully done",data:{jobs:j,users:u}}));
})

4 Answers 4

14

Schema.find() is an async function. So your last line of code will execute while you wait for the first job search is executed in your loop. I suggest change it to Promises and use Promise.all(array).

To do so, first you have to change to use Promise with mongoose. you can do this with bluebird like this:

var mongoose = require('mongoose');
mongoose.Promise = require('bluebird');

Then you can use Promises instead of callbacks like this:

userSchema.find({}).then(function(users) {
  var jobQueries = [];

  users.forEach(function(u) {
    jobQueries.push(jobSchema.find({u_sno:s.u.sno}));
  });

  return Promise.all(jobQueries );
}).then(function(listOfJobs) {
    res.send(listOfJobs);
}).catch(function(error) {
    res.status(500).send('one of the queries failed', error);
});

EDIT How to list both jobs and users

If you want to have a structure like:

[{ 
  user: { /* user object */,
  jobs: [ /* jobs */ ]
}]

you could merge the lists together. listOfJobs is in the same order as the jobQueries list, so they are in the same order as the users. Save users to a shared scope to get access to the list in the 'then function' and then merge.

..
}).then(function(listOfJobs) {
  var results = [];

  for (var i = 0; i < listOfJobs.length; i++) {
    results.push({
      user: users[i],
      jobs: listOfJobs[i]
    });
  }

  res.send(results);
}).catch(function(error) {
  res.status(500).send('one of the queries failed', error);
});
Sign up to request clarification or add additional context in comments.

5 Comments

thank for help but i have one question that how can print both list of jobs and users
show this error---->App Started on PORT 8080 express deprecated res.send(status, body): Use res.status(status).send(body) instead App/Controllers/Api/MarketController.js:36:23 (node:15265) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): RangeError: Invalid status code: 0 (node:15265) DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
you still need the catch.. let me update my answer from '...' to the catch
I'd like to add to this that it only worked for me once I added a callback to the line jobQueries.push(jobSchema.find({u_sno:s.u.sno})); so it became jobQueries.push(jobSchema.find({u_sno:s.u.sno}), () => {});
@LuisEgan Try to add mongoose.Promise = global.Promise where you connect to mongoose. Bluebird is an es5 Promise lib. but with later versions of node, you have Promise in global
6

A nice elegant solution is to use the cursor.eachAsync() function. Credit to https://thecodebarbarian.com/getting-started-with-async-iterators-in-node-js.

The eachAsync() function executes a (potentially async) function for each document that the cursor returns. If that function returns a promise, it will wait for that promise to resolve before getting the next document. This is the easiest way to exhaust a cursor in mongoose.

  // A cursor has a `.next()` function that returns a promise. The promise
  // will resolve to the next doc if there is one, or null if they are no
  // more results.
  const cursor = MyModel.find().sort({name: 1 }).cursor();

  let count = 0;
  console.log(new Date());
  await cursor.eachAsync(async function(doc) {
    // Wait 1 second before printing first doc, and 0.5 before printing 2nd
    await new Promise(resolve => setTimeout(() => resolve(), 1000 - 500 * (count++)));
    console.log(new Date(), doc);
  });

Comments

2

No need to use forEach() which is synchronous and being called in an asynchronous fashion, that will give you wrong results.

You can use the aggregation framework and use $lookup which performs a left outer join to another collection in the same database to filter in documents from the "joined" collection for processing.

So the same query can be done using a single aggregation pipeline as:

userSchema.aggregate([
    {
        "$lookup": {
            "from": "jobs", /* underlying collection for jobSchema */
            "localField": "sno",
            "foreignField": "u_sno",
            "as": "jobs"
        }
    }
]).exec(function(err, docs){
    if (err) throw err;
    res.send(
        JSON.stringify({
            status: "success",
            message: "successfully done",
            data: docs
        })
    );
})

4 Comments

suggest for i need inner join like we need both result users and jos something one to one table
What's your MongoDB server version?
its 3.2 version
@chridam mongodb doesn't supports inner join, instead it supports only left outer join.
0

You can use this:

db.collection.find(query).forEach(function(err, doc) {
   // ...
});

2 Comments

I have gain an error is: .forEach is not a function. Why?
because foreach is a native mongodb function not a mongoose function

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.