web-dev-qa-db-ja.com

mongooseで2つのコレクションに参加する方法

次のように定義された2つのスキーマがあります。

var WorksnapsTimeEntry = BaseSchema.extend({
 student: {
     type: Schema.ObjectId,
     ref: 'Student'
 },
 timeEntries: {
     type: Object
 }
 });

var StudentSchema = BaseSchema.extend({
firstName: {
    type: String,
    trim: true,
    default: ''
    // validate: [validateLocalStrategyProperty, 'Please fill in your first name']
},
lastName: {
    type: String,
    trim: true,
    default: ''
    // validate: [validateLocalStrategyProperty, 'Please fill in your last name']
},
displayName: {
    type: String,
    trim: true
},
municipality: {
    type: String
    }
});

そして、私は各学生をループして、それが時間エントリであることを示したいと思います。これまでのところ、WorksnapTimeEntryスキーマテーブルにどのように参加すればよいかまだわからないため、明らかに正しくないこのコードがあります。

Student.find({ status: 'student' })
        .populate('student')
        .exec(function (err, students) {
            if (err) {
                return res.status(400).send({
                    message: errorHandler.getErrorMessage(err)
                });
            }
            _.forEach(students, function (student) {
               // show student with his time entries....
            });
            res.json(students);
        });

私はそのようなことをどのように達成するのか知っていますか?

8
Hisni Fazlija

ここでは.populate()は必要ありませんが、代わりに2つのクエリが必要です。最初のクエリはStudentオブジェクトに一致して__id_値を取得し、2番目のクエリは _$in_ これらの「生徒」のそれぞれのWorksnapsTimeEntryアイテムに一致します。

インデントのクリープを避けるためだけに _async.waterfall_ を使用する:

_async.waterfall(
    [
        function(callback) {
          Student.find({ "status": "student" },{ "_id": 1 },callback);
        },
        function(students,callback) {
            WorksnapsTimeEntry.find({
                "student": { "$in": students.map(function(el) {
                    return el._id
                })
            },callback);
        }
    ],
    function(err,results) {
       if (err) {
          // do something
       } else {
          // results are the matching entries
       }
    }
)
_

本当に必要な場合は、2番目のクエリで.populate("student")を使用して、他のテーブルからアイテムを取得できます。

逆の場合は、WorksnapsTimeEntryでクエリを実行して「すべて」を返し、「match」クエリオプションを使用して.populate()からのnullの結果を除外します。

_WorksnapsTimeEntry.find().populate({
    "path": "student",
    "match": { "status": "student" }
}).exec(function(err,entries) {
   // Now client side filter un-matched results
   entries = entries.filter(function(entry) {
       return entry.student != null;
   });
   // Anything not populated by the query condition is now removed
});
_

「データベース」は結果の大部分をフィルタリングしないため、これは望ましいアクションではありません。

そうしない正当な理由がない限り、データを「埋め込む」必要があります。そうすれば、_"status_ "のようなプロパティは既にコレクションで利用でき、追加のクエリは必要ありません。

MongoDBのようなNoSQLソリューションを使用している場合、リレーショナル設計の原則に固執するのではなく、その概念を採用する必要があります。リレーショナルモデリングを一貫して行っている場合は、リレーショナルデータベースを使用することもできます。それを処理する他の方法を備えたソリューションから利益を得ることはないからです。

10
Blakes Seven

バージョン3.2では、集約パイプラインで $ lookup を使用して、左外部結合を実行できます。

Student.aggregate([{
    $lookup: {
        from: "worksnapsTimeEntries", // collection name in db
        localField: "_id",
        foreignField: "student",
        as: "worksnapsTimeEntries"
    }
}]).exec(function(err, students) {
    // students contain WorksnapsTimeEntries
});
23
Talha Awan