无论您怎么看,只要您有这样的规范化关系,您就需要两个查询来获取包含“tasks”集合中的详细信息并填写“projects”集合中的详细信息的结果。 MongoDB 不以任何方式使用连接,mongoose 也不例外。 Mongoose 确实提供了.populate(),但这只是方便魔术,因为它本质上是运行另一个查询并将结果合并到引用的字段值上。
因此,在这种情况下,您最终可能会考虑将项目信息嵌入任务中。当然会有重复,但使用单一集合会使查询模式更加简单。
将集合与引用模型分开,您基本上有两种方法。但首先您可以使用aggregate 以获得更符合您实际需求的结果:
Task.aggregate(
[
{ "$group": {
"_id": "$projectId",
"completed": {
"$sum": {
"$cond": [ "$completed", 1, 0 ]
}
},
"incomplete": {
"$sum": {
"$cond": [ "$completed", 0, 1 ]
}
}
}}
],
function(err,results) {
}
);
这仅使用$group 管道来累积“tasks”集合中“projectid”的值。为了计算“完成”和“未完成”的值,我们使用$cond 运算符,它是一个三元组来决定将哪个值传递给$sum。由于此处的第一个或“if”条件是布尔评估,因此现有的布尔“完整”字段将执行,将 where true 传递给“then”或“else”并传递第三个参数。
这些结果没有问题,但它们不包含来自“项目”集合中收集的“_id”值的任何信息。使输出看起来像这种方式的一种方法是从返回的“结果”对象的聚合结果回调中调用 .populate() 的模型形式:
Project.populate(results,{ "path": "_id" },callback);
在这种形式中,.populate() 调用将一个对象或数据数组作为它的第一个参数,第二个是人口的选项文档,这里的必填字段是“路径”。这将处理任何项目并从被调用的模型中“填充”,将这些对象插入回调中的结果数据中。
作为一个完整的示例清单:
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() 适用于这种聚合结果,即使是有效的另一个查询,通常应该适用于大多数用途。然而,清单中包含一个特定示例,其中创建了“两个”项目,但当然只有“一个”任务仅引用其中一个项目。
由于聚合正在处理“任务”集合,因此它对任何未在其中引用的“项目”一无所知。为了获得包含计算总数的完整“项目”列表,您需要更具体地运行两个查询并“合并”结果。
这基本上是对不同键和数据的“哈希合并”,但是对此的好帮手是一个名为 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
}
]
这列出了来自每个“项目”的数据,并包括来自与其相关的“任务”集合的计算值。
因此,您可以采取一些方法。同样,您最终可能最好将“任务”嵌入到“项目”项中,这又是一种简单的聚合方法。如果您要嵌入任务信息,那么您也可以在“项目”对象上维护“完成”和“未完成”计数器,并在任务数组中使用$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
);
您必须知道当前任务状态才能使计数器更新正常工作,但这很容易编写代码,您可能至少应该在传递给您的方法的对象中包含这些详细信息。
就我个人而言,我会重新建模为后一种形式并这样做。您可以执行查询“合并”,如此处的两个示例所示,但这当然是有代价的。