これをどのように見ても、このような正規化された関係がある限り、「タスク」コレクションの詳細を含む結果と「プロジェクト」コレクションの詳細を入力する結果を取得するには、2つのクエリが必要になります。 MongoDBは結合をまったく使用せず、mongooseも例外ではありません。マングースは.populate()
を提供します 、ただし、これは、基本的に別のクエリを実行し、参照されたフィールド値に結果をマージするための便利な魔法にすぎません。
したがって、これは、最終的にプロジェクト情報をタスクに埋め込むことを検討する可能性がある1つのケースです。もちろん重複はありますが、単一のコレクションを使用すると、クエリパターンがはるかに簡単になります。
コレクションを参照モデルで分離しておくと、基本的に2つのアプローチがあります。ただし、最初に集計 を使用できます。 実際の要件に沿った結果を得るには:
Task.aggregate(
[
{ "$group": {
"_id": "$projectId",
"completed": {
"$sum": {
"$cond": [ "$completed", 1, 0 ]
}
},
"incomplete": {
"$sum": {
"$cond": [ "$completed", 0, 1 ]
}
}
}}
],
function(err,results) {
}
);
これは、 $ group
を使用するだけです。
「tasks」コレクションを使用して「projectid」の値を蓄積するためのパイプライン。 「completed」と「incomplete」の値をカウントするために、 $ cond
<に渡す値を決定する3進数の演算子code> $ sum
。ここでの最初の条件または「if」条件はブール評価であるため、既存のブール「complete」フィールドが実行され、 true
が渡されます。 「then」または「else」に3番目の引数を渡します。
これらの結果は問題ありませんが、収集された「_id」値の「プロジェクト」コレクションからの情報は含まれていません。出力をこのように見せるための1つのアプローチは、 .populate()
のモデル形式を呼び出すことです。 返された「結果」オブジェクトの集計結果コールバック内から:
Project.populate(results,{ "path": "_id" },callback);
この形式では、 .populate()
callは、最初の引数としてオブジェクトまたはデータの配列を取ります。2番目の引数は、母集団のオプションドキュメントです。ここで、必須フィールドは「パス」用です。これにより、すべてのアイテムが処理され、コールバックの結果データにそれらのオブジェクトを挿入するように呼び出されたモデルから「入力」されます。
完全な例のリストとして:
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()
この種の集計結果には、効果的に別のクエリとしてもうまく機能し、通常、ほとんどの目的に適しているはずです。ただし、リストには「2つの」プロジェクトが作成されている特定の例が含まれていますが、もちろん、1つのプロジェクトのみを参照する「1つの」タスクのみです。
アグリゲーションは「タスク」コレクションに取り組んでいるため、そこで参照されていない「プロジェクト」については何も知りません。計算された合計を含む「プロジェクト」の完全なリストを取得するには、2つのクエリを実行し、結果を「マージ」する際に、より具体的にする必要があります。
これは基本的に、個別のキーとデータの「ハッシュマージ」ですが、このための優れたヘルパーは、nedb<と呼ばれるモジュールです。 / a> 、これにより、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
}
]
これには、すべての「プロジェクト」からのデータが一覧表示され、それに関連する「タスク」コレクションから計算された値が含まれます。
したがって、実行できるアプローチがいくつかあります。繰り返しになりますが、最終的には、代わりに「タスク」を「プロジェクト」アイテムに埋め込むのが最善の場合があります。これも単純な集計アプローチです。また、タスク情報を埋め込む場合は、「プロジェクト」オブジェクトに「完了」と「未完了」のカウンターを維持し、タスク配列で $ 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
);
カウンターの更新が正しく機能するためには、現在のタスクステータスを知る必要がありますが、これはコーディングが簡単であり、メソッドに渡されるオブジェクトに少なくともこれらの詳細が含まれている必要があります。
個人的には後者の形に改造してそれを行います。ここで2つの例に示されているように、クエリ「マージ」を実行できますが、もちろんコストがかかります。