在节点中使用异步 mongodb 查询的嵌套循环

Nested loops with async mongodb queries in node

问题

我在 MongoDB

中有 3 个合集

我正在尝试编写一个函数,该函数将从区域集合开始,对于第一个区域,它会获取每个单独的农场 ID,并使用它来查询其他两个集合,以获得总产量和奶牛总数对于该农场,然后对每个农场求和以获得该地区的总数。

尝试

我首先尝试对一个区域使用直接的 mongodb 调用

var db = client.connect('mongodb://localhost:27017/mydb', function(err,db) {
  if (err) throw err;

  var regions = db.collection('Regions');
  var details = db.collection('Details');
  var yield = db.collection('Yield');

regions.find({"region" : "Gotham"}).toArray(function(err, docs) {

  for (var k = 0; k < docs.length; k++) {
    var regionYield = 0;

    for (var j = 0; j < docs[k].farms.length; j++) {
      var farmYield = 0;
      var farmID = docs[k].farms[j]
      yield.find({Farm_ID: farmID}).toArray(function(err, docs) {

        for (var i = 0; i < docs.length; i++) {
          farmYield += +docs[i].Yield;
        }
        console.log('finished inner loop');

        regionYield += farmYield;
      });
    }
    console.log('finished middle loop');
  }
  console.log('finished outer loop');
});

外循环完成后,我想用最终的 regionYield 值做一些事情,但是按照现在的结构,外循环在内部循环完成必要的计算之前完成,由于到异步 mongo 调用。我只是想不出解决这个问题。我在这里看了很多 questions/answers 解释回调,但我就是不知道如何将它应用到我的案例中。

您可以使用 async 库来更轻松地处理嵌套异步调用。

我认为您在设计中采用了错误的方式,稍后会详细介绍。首先是基本更改您的列表。

简单且没有额外依赖的方法是使用驱动程序直接支持的节点stream interface。这允许您依次处理每个文档,并且不会像 .toArray().

那样将所有内容加载到内存中

还有一个 "stream" 有一个触发的 "end" 事件,以及可以包装发出的查询的自然流量控制:

var mongodb = require('mongodb'),
    MongoClient = mongodb.MongoClient;


MongoClient.connect('mongodb://localhost/mydb',function(err,db) {
  if (err) throw err;

  var regions = db.collection('Regions'),
      yield = db.collection('Yield');

  var resultHash = {};

  var rstream = regions.find({ "region": "Gotham" });

  rstream.on('err',function(err) {
    throw err;
  });

  rstream.on('end',function() {
    console.log( 'Complete' );
    console.log( JSON.stringify( resultHash, undefined, 2 ) );
    db.close();
  });

  rstream.on('data',function(region) {
    rstream.pause();                  // pause outer stream

    resultHash[region.region] = 0;

    var fstream = yield.find({ "Farm_ID": { "$in": region.farms } });

    fstream.on('err',function(err) {
      throw err;
    });

    fstream.on('end',function() {
      console.log('finished farms');
      rstream.resume();i              // resumes outer stream
    });

    fstream.on('data',function(farm) {
      fstream.pause();                // pause inner stream
      resultHash[region.region] += farm.Yield;
      fstream.resume();               // resume inner stream
    });

  });

});

这基本上是 "sum up" 其他文档的匹配 "region" 的所有 "Yield" 值。另请注意 $in 的非常简单的用法来传递数组中已经存在的所有 "farms" 而不是处理另一个循环。

但无论如何你真的不应该这样做。数据库 "smart",您的设计需要更智能。

您基本上可以通过将 "region" 数据添加到 "yield" 数据来避免这里的所有麻烦。那么这只是运行宁.aggregate():

的问题

所以在 "yield" 中有这样的数据:

{ "region": "Gotham", "Yield": 123 }

然后 运行 只是这个代码:

yield.aggregate(
    [
        { "$group": {
            "_id": "$region",
            "yield": { "$sum": "$Yield" }
        }}
    ],
    function(err,results) {

    }
);

这一切都完成了,代码中没有循环或计算。

因此,如果您只是将 "related data"(例如 "region")添加到您要使用的 "yield" 数据中,那么 MongoDB 已经拥有制作工具轻而易举地积累那个关键。

这就是摆脱关系设​​计的意义。事情的运作方式不同,因此您需要以不同的方式与他们合作。也更聪明。

您还可以在循环中使用 let 而不是 var。

var db = client.connect('mongodb://localhost:27017/mydb', function(err,db) {
  if (err) throw err;

  var regions = db.collection('Regions');
  var details = db.collection('Details');
  var yield = db.collection('Yield');

regions.find({"region" : "Gotham"}).toArray(function(err, docs) {

  for (let k = 0; k < docs.length; k++) {
    var regionYield = 0;

    for (let j = 0; j < docs[k].farms.length; j++) {
      var farmYield = 0;
      var farmID = docs[k].farms[j]
      yield.find({Farm_ID: farmID}).toArray(function(err, docs) {

        for (let i = 0; i < docs.length; i++) {
          farmYield += +docs[i].Yield;
        }
        console.log('finished inner loop');

        regionYield += farmYield;
      });
    }
    console.log('finished middle loop');
  }
  console.log('finished outer loop');
});

那会很好..