MongoDB
 sql >> Teknologi Basis Data >  >> NoSQL >> MongoDB

MongoDB menggabungkan jumlah item koleksi terkait dengan hasil koleksi lainnya

Apa pun cara Anda melihatnya, selama Anda memiliki hubungan yang dinormalisasi seperti ini maka Anda akan memerlukan dua kueri untuk mendapatkan hasil yang berisi detail dari koleksi "tugas" dan mengisi dengan detail dari koleksi "proyek". MongoDB tidak menggunakan gabungan dengan cara apa pun, dan luwak tidak berbeda. Luwak memang menawarkan .populate() , tapi itu hanya keajaiban kenyamanan untuk apa yang pada dasarnya menjalankan kueri lain dan menggabungkan hasil pada nilai bidang yang direferensikan.

Jadi ini adalah satu kasus di mana mungkin Anda akhirnya dapat mempertimbangkan untuk menyematkan informasi proyek dalam tugas. Tentu saja akan ada duplikasi, tetapi itu membuat pola kueri jauh lebih sederhana dengan koleksi tunggal.

Dengan memisahkan koleksi dengan model yang direferensikan, pada dasarnya Anda memiliki dua pendekatan. Tetapi pertama-tama Anda dapat menggunakan agregat untuk mendapatkan hasil yang lebih sesuai dengan kebutuhan Anda yang sebenarnya:

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

        }
    );

Ini hanya menggunakan $group pipa untuk terakumulasi pada nilai "projectid" dalam koleksi "tugas". Untuk menghitung nilai "selesai" dan "tidak lengkap" kami menggunakan $cond operator yang merupakan ternary untuk memutuskan nilai mana yang akan diteruskan ke $sum . Karena kondisi pertama atau "jika" di sini adalah evaluasi boolean, maka bidang "lengkap" boolean yang ada akan melakukannya, meneruskan di mana true untuk "kemudian" atau "lain" melewati argumen ketiga.

Hasil tersebut baik-baik saja tetapi tidak mengandung informasi apa pun dari kumpulan "proyek" untuk nilai "_id" yang dikumpulkan. Salah satu pendekatan untuk membuat keluaran terlihat seperti ini adalah dengan memanggil bentuk model .populate() dari dalam panggilan balik hasil agregasi pada objek "hasil" yang dikembalikan:

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

Dalam formulir ini .populate() panggilan mengambil objek atau larik data sebagai argumen pertama, dengan yang kedua menjadi dokumen opsi untuk populasi, di mana bidang wajib di sini adalah untuk "jalur". Ini akan memproses item apa pun dan "mengisi" dari model yang dipanggil dengan memasukkan objek tersebut ke dalam data hasil dalam panggilan balik.

Sebagai daftar contoh lengkap:

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();
  }
);

Dan ini akan memberikan hasil seperti ini:

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

Jadi .populate() bekerja dengan baik untuk hasil agregasi semacam ini, sama efektifnya dengan kueri lain, dan umumnya harus sesuai untuk sebagian besar tujuan. Namun ada contoh spesifik yang disertakan dalam daftar di mana ada "dua" proyek yang dibuat tetapi tentu saja hanya "satu" tugas yang merujuk hanya satu proyek.

Karena agregasi sedang mengerjakan koleksi "tugas", ia tidak memiliki pengetahuan apa pun tentang "proyek" apa pun yang tidak dirujuk di sana. Untuk mendapatkan daftar lengkap "proyek" dengan total yang dihitung, Anda harus lebih spesifik dalam menjalankan dua kueri dan "menggabungkan" hasilnya.

Ini pada dasarnya adalah "gabungan hash" pada kunci dan data yang berbeda, namun penolong yang baik untuk ini adalah modul yang disebut nedb , yang memungkinkan Anda untuk menerapkan logika dengan cara yang lebih konsisten dengan kueri dan operasi MongoDB.

Pada dasarnya Anda ingin salinan data dari koleksi "proyek" dengan bidang yang ditambah, lalu Anda ingin "menggabungkan" atau .update() informasi tersebut dengan hasil agregasi. Sekali lagi sebagai daftar lengkap untuk ditunjukkan:

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();
  }

Dan hasilnya di sini:

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

Itu mencantumkan data dari setiap "proyek" dan menyertakan nilai yang dihitung dari kumpulan "tugas" yang terkait dengannya.

Jadi ada beberapa pendekatan yang bisa Anda lakukan. Sekali lagi, Anda mungkin pada akhirnya lebih baik hanya menyematkan "tugas" ke dalam item "proyek", yang lagi-lagi akan menjadi pendekatan agregasi sederhana. Dan jika Anda akan menyematkan informasi tugas, maka Anda juga dapat mempertahankan penghitung untuk "lengkap" dan "tidak lengkap" pada objek "proyek" dan cukup perbarui ini karena item ditandai selesai dalam larik tugas dengan $inc operator.

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
);

Anda harus mengetahui status tugas saat ini agar pembaruan penghitung berfungsi dengan benar, tetapi ini mudah dikodekan dan Anda mungkin harus memiliki setidaknya detail tersebut dalam objek yang diteruskan ke metode Anda.

Secara pribadi saya akan memodelkan ulang ke bentuk yang terakhir dan melakukan itu. Anda dapat melakukan kueri "penggabungan" seperti yang ditunjukkan dalam dua contoh di sini, tetapi tentu saja ada biayanya.



  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Caching hasil kueri berulang di MongoDB

  2. Tidak dapat menghitung waktu rata-rata

  3. Kembalikan hanya elemen sub-dokumen yang cocok dalam array bersarang

  4. Repositori mongodb data pegas ekspresi reguler

  5. Cara Mengganti Dokumen yang Ada Saat Mengimpor File ke MongoDB