在节点中使用异步 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');
});
那会很好..
问题
我在 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');
});
那会很好..