MongoDB将相关的收集项目数与其他收集结果合并

我是新来的MongoDB,并试图找出如何有效地查询集合中的每个项目。

我有projects集合和tasks集合

 //projects { _id: ObjectId(), name: String } //tasks { _id: ObjectId(), projectId: ObjectId(), //reference project id completed: Bool } 

我想获得所有的项目,然后计算每个项目已completedincomplete任务

 db.projects.find({})... //perhaps something similar in output [ { _id: ObjectId(), //projectId name: String completed: Number, incomplete: Number } ] 

我使用mongoose作为ORM。 我不知道这是可能的mongoose,甚至本地mongodb查询。 感谢任何帮助。 谢谢!

无论你如何看待这个问题,只要你有这样的规范化的关系,那么你将需要两个查询来得到一个包含来自“tasks”集合的细节的结果,并填写“projects”集合中的细节。 MongoDB不以任何方式使用连接,mongoose也不例外。 Mongoose确实提供了.populate() ,但对于本质上正在运行另一个查询并将结果合并到所引用字段值的内容来说,这只是方便的魔法。

所以这是一个最终可能会考虑将项目信息embedded到任务中的情况。 当然会有重复,但它使得查询模式更加简单,只有一个单一的集合。

保持集合与参考模型分离,你基本上有两种方法。 但首先你可以使用聚合来获得更多的实际需求的结果:

  Task.aggregate( [ { "$group": { "_id": "$projectId", "completed": { "$sum": { "$cond": [ "$completed", 1, 0 ] } }, "incomplete": { "$sum": { "$cond": [ "$completed", 0, 1 ] } } }} ], function(err,results) { } ); 

这只是使用一个$grouppipe道,以积累在“任务”集合“projectid”的价值。 为了统计“已完成”和“不完整”的值,我们使用$cond运算符作为三元来决定将哪个值传递给$sum 。 由于这里的第一个或“if”条件是一个布尔评估,所以现有的布尔“complete”字段将执行,将true传递给传递第三个参数的“then”或“else”。

这些结果是可以的,但是它们不包含收集的“_id”值的“项目”集合中的任何信息。 使输出看起来如此的一种方法是从返回的“results”对象的聚合结果callback中调用.populate()的模型forms:

  Project.populate(results,{ "path": "_id" },callback); 

在这种forms下, .populate()调用将一个对象或数据数组作为第一个参数,第二个是用于填充的选项文档,其中这里的必填字段是“path”。 这将处理任何项目,并从被称为将这些对象插入到callback中的结果数据的模型中“填充”。

作为完整的示例列表:

 var async = require('async'), mongoose = require('mongoose'), Schema = mongoose.Schema; var projectSchema = new Schema({ "name": String }); var taskSchema = new Schema({ "projectId": { "type": Schema.Types.ObjectId, "ref": "Project" }, "completed": { "type": Boolean, "default": false } }); var Project = mongoose.model( "Project", projectSchema ); var Task = mongoose.model( "Task", taskSchema ); mongoose.connect('mongodb://localhost/test'); async.waterfall( [ function(callback) { async.each([Project,Task],function(model,callback) { model.remove({},callback); }, function(err) { callback(err); }); }, function(callback) { Project.create({ "name": "Project1" },callback); }, function(project,callback) { Project.create({ "name": "Project2" },callback); }, function(project,callback) { Task.create({ "projectId": project },callback); }, function(task,callback) { Task.aggregate( [ { "$group": { "_id": "$projectId", "completed": { "$sum": { "$cond": [ "$completed", 1, 0 ] } }, "incomplete": { "$sum": { "$cond": [ "$completed", 0, 1 ] } } }} ], function(err,results) { if (err) callback(err); Project.populate(results,{ "path": "_id" },callback); } ); } ], function(err,results) { if (err) throw err; console.log( JSON.stringify( results, undefined, 4 )); process.exit(); } ); 

这会给出如下结果:

 [ { "_id": { "_id": "54beef3178ef08ca249b98ef", "name": "Project2", "__v": 0 }, "completed": 0, "incomplete": 1 } ] 

所以.populate()对于这种聚合结果的效果很好,即使是另一个有效的查询,并且通常适用于大多数目的。 然而,在列表中包含了一个具体的例子,其中创build了“两个”项目,当然仅仅涉及一个项目的“一个”任务。

由于聚合工作在“任务”集合上,所以它不知道任何没有引用的“项目”。 为了获得计算总数的“项目”的完整列表,您需要更具体地运行两个查询和“合并”结果。

这基本上是对不同的键和数据进行“哈希合并”,但是对于这个好的帮助器是一个名为nedb的模块,它允许你以更加符合MongoDB查询和操作的方式应用逻辑。

基本上你想从“项目”集合的数据的副本扩大字段,然后你想要“合并”或.update()该信息与聚合结果。 再次作为一个完整的清单来演示:

 var async = require('async'), mongoose = require('mongoose'), Schema = mongoose.Schema, DataStore = require('nedb'), db = new DataStore(); var projectSchema = new Schema({ "name": String }); var taskSchema = new Schema({ "projectId": { "type": Schema.Types.ObjectId, "ref": "Project" }, "completed": { "type": Boolean, "default": false } }); var Project = mongoose.model( "Project", projectSchema ); var Task = mongoose.model( "Task", taskSchema ); mongoose.connect('mongodb://localhost/test'); async.waterfall( [ function(callback) { async.each([Project,Task],function(model,callback) { model.remove({},callback); }, function(err) { callback(err); }); }, function(callback) { Project.create({ "name": "Project1" },callback); }, function(project,callback) { Project.create({ "name": "Project2" },callback); }, function(project,callback) { Task.create({ "projectId": project },callback); }, function(task,callback) { async.series( [ function(callback) { Project.find({},function(err,projects) { async.eachLimit(projects,10,function(project,callback) { db.insert({ "projectId": project._id.toString(), "name": project.name, "completed": 0, "incomplete": 0 },callback); },callback); }); }, function(callback) { Task.aggregate( [ { "$group": { "_id": "$projectId", "completed": { "$sum": { "$cond": [ "$completed", 1, 0 ] } }, "incomplete": { "$sum": { "$cond": [ "$completed", 0, 1 ] } } }} ], function(err,results) { async.eachLimit(results,10,function(result,callback) { db.update( { "projectId": result._id.toString() }, { "$set": { "complete": result.complete, "incomplete": result.incomplete } }, callback ); },callback); } ); }, ], function(err) { if (err) callback(err); db.find({},{ "_id": 0 },callback); } ); } ], function(err,results) { if (err) throw err; console.log( JSON.stringify( results, undefined, 4 )); process.exit(); } 

结果在这里:

 [ { "projectId": "54beef4c23d4e4e0246379db", "name": "Project2", "completed": 0, "incomplete": 1 }, { "projectId": "54beef4c23d4e4e0246379da", "name": "Project1", "completed": 0, "incomplete": 0 } ] 

列出来自每个“项目”的数据,并包含来自与其相关的“任务”集合的计算值。

所以有几种方法可以做。 再一次,你可能最好最好是将“任务”embedded到“项目”项目中,而这又将是一个简单的聚合方法。 如果你要embedded任务信息,那么你也可以在“项目”对象上维护“完成”和“不完整”的计数器,只需要用$inc操作符在任务数组中标记完成项目。

 var taskSchema = new Schema({ "completed": { "type": Boolean, "default": false } }); var projectSchema = new Schema({ "name": String, "completed": { "type": Number, "default": 0 }, "incomplete": { "type": Number, "default": 0 } "tasks": [taskSchema] }); var Project = mongoose.model( "Project", projectSchema ); // cheat for a model object with no collection var Task = mongoose.model( "Task", taskSchema, undefined ); // Then in later code // Adding a task var task = new Task(); Project.update( { "task._id": { "$ne": task._id } }, { "$push": { "tasks": task }, "$inc": { "completed": ( task.completed ) ? 1 : 0, "incomplete": ( !task.completed ) ? 1 : 0; } }, callback ); // Removing a task Project.update( { "task._id": task._id }, { "$pull": { "tasks": { "_id": task._id } }, "$inc": { "completed": ( task.completed ) ? -1 : 0, "incomplete": ( !task.completed ) ? -1 : 0; } }, callback ); // Marking complete Project.update( { "tasks": { "$elemMatch": { "_id": task._id, "completed": false } }}, { "$set": { "tasks.$.completed": true }, "$inc": { "completed": 1, "incomplete": -1 } }, callback ); 

您必须知道当前的任务状态,但计数器更新才能正常工作,但这很容易编写,您可能至less应将这些详细信息传递到您的方法中。

就我个人而言,我会重新塑造到后者的forms,并做到这一点。 你可以在这里做两个例子来进行查询“合并”,但这当然是有代价的。

当需要在MongoDB中对事物进行分组或计数时,通常需要使用聚合框架 。 以下是如何在shell中计数您的数据:

 db.tasks.aggregate([ {$group: { _id: {projectID: "$projectID", completed: "$completed"}, count: {$sum: 1} }}); 

这将为项目中的每个任务返回两个文件 – 一个包含已完成任务的计数,另一个包含尚未完成的任务。

我从来没有使用mongoose,但现在你有一些从开始:)