Node.js MongoDB将相关集合项计数与其他集合结果合并
我是mongodb新手,正在尝试找出如何高效地查询集合中的每个项目 我有Node.js MongoDB将相关集合项计数与其他集合结果合并,node.js,mongodb,mongoose,mongodb-query,aggregation-framework,Node.js,Mongodb,Mongoose,Mongodb Query,Aggregation Framework,我是mongodb新手,正在尝试找出如何高效地查询集合中的每个项目 我有项目收藏和任务收藏 //projects { _id: ObjectId(), name: String } //tasks { _id: ObjectId(), projectId: ObjectId(), //reference project id completed: Bool } 我想获得所有项目,然后计算每个项目的已完成和未完成任务 db.projects.find({})...
项目
收藏和任务
收藏
//projects
{
_id: ObjectId(),
name: String
}
//tasks
{
_id: ObjectId(),
projectId: ObjectId(), //reference project id
completed: Bool
}
我想获得所有项目,然后计算每个项目的已完成
和未完成
任务
db.projects.find({})...
//perhaps something similar in output
[
{
_id: ObjectId(), //projectId
name: String
completed: Number,
incomplete: Number
}
]
我用猫鼬做ORM。我不知道这在mongoose甚至本机mongodb查询中是否可行。谢谢你的帮助。谢谢 当您需要在MongoDB中分组或计数时,通常需要使用。下面是如何计算shell中的数据:
db.tasks.aggregate([ {$group: {
_id: {projectID: "$projectID", completed: "$completed"},
count: {$sum: 1}
}});
这将为项目中的每个任务返回两个文档——一个包含已完成任务的计数,另一个包含尚未完成的任务
我从未使用过Mongoose,但现在您可以从以下内容开始:)无论您如何看待这一点,只要您有一个这样的规范化关系,那么您将需要两个查询来获得一个结果,其中包含“任务”集合中的详细信息,并填写“项目”集合中的详细信息。MongoDB不以任何方式使用连接,mongoose也不例外。Mongoose确实提供了
.populate()
,但这只是运行另一个查询并合并引用字段值的结果的便利魔法
<>这是一个你可能最终会考虑将项目信息嵌入到任务中的情况。当然会有重复,但是使用单个集合会使查询模式更加简单
将集合与引用模型分开,基本上有两种方法。但首先,您可以使用以下工具,以便更符合您的实际需求:
Task.aggregate(
[
{ "$group": {
"_id": "$projectId",
"completed": {
"$sum": {
"$cond": [ "$completed", 1, 0 ]
}
},
"incomplete": {
"$sum": {
"$cond": [ "$completed", 0, 1 ]
}
}
}}
],
function(err,results) {
}
);
这只是使用管道来累积“tasks”集合中“projectd”的值。为了计算“已完成”和“未完成”的值,我们使用三元运算符来决定传递给哪个值。由于这里的第一个或“if”条件是布尔求值,那么现有的布尔“complete”字段就可以了,将wheretrue
传递给传递第三个参数的“then”或“else”
这些结果是可以的,但它们不包含来自“项目”集合中收集的“\u id”值的任何信息。使输出看起来如此的一种方法是从返回的“results”对象的聚合结果回调中调用.populate()
的模型形式:
在这种形式下,.populate()
调用将对象或数据数组作为第一个参数,第二个参数是用于填充的选项文档,此处的必填字段是“path”。这将处理所有项,并从调用的模型中“填充”,将这些对象插入回调中的结果数据中
作为完整的示例列表:
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()
适用于这种聚合结果,甚至与另一个查询一样有效,并且通常适用于大多数目的。然而,清单中包含了一个特定示例,其中创建了“两个”项目,但当然只有“一个”任务引用了其中一个项目
由于聚合处理的是“任务”集合,因此它不知道其中未引用的任何“项目”。为了得到一个包含计算总数的“项目”的完整列表,您需要更具体地运行两个查询并“合并”结果
这基本上是一个针对不同键和数据的“散列合并”,不过这是一个名为的模块,它允许您以与MongoDB查询和操作更一致的方式应用逻辑
基本上,您需要一份来自“projects”集合的数据副本,其中包含扩展字段,然后您需要将该信息与聚合结果“合并”或.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
}
]
它列出了来自每个“项目”的数据,并包括来自与其相关的“任务”集合的计算值
因此,有几种方法可以做到。同样,您最好还是将“任务”嵌入到“项目”项中,这也是一种简单的聚合方法。如果要嵌入任务信息,那么最好在“project”对象上维护“complete”和“complete”计数器,并在任务数组中使用运算符标记为completed的项目时更新这些计数器
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
);
您必须知道当前任务状态,计数器更新才能正常工作,但这很容易编码,并且您可能至少应该在传递到方法的对象中拥有这些详细信息
就我个人而言,我会以后一种形式重新建模并这样做。您可以像这里的两个示例中所示的那样进行查询“合并”,但这当然要付出代价。因此,我必须运行两个查询,一个用于查找所有项目,另一个用于聚合计数并在客户端进行映射,对吗?只有当您需要从项目中获取一些额外数据时,谢谢,这是一个很好的答案!是的,我考虑过项目中的嵌入式任务,但我担心我的项目文档可能会达到文档大小限制。您可以始终
$pull
从阵列中提取已完成的任务,或者至少通过一些逻辑对总任务保持“上限”。也许是自定义逻辑,只保留这么多“已完成”的项目。根据您真正想要做的事情,阵列中的项目少于500项时,性能不应该真正降低。
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
);