MongoDB
 sql >> Baza danych >  >> NoSQL >> MongoDB

MongoDB łączy liczbę elementów kolekcji powiązanych z innymi wynikami kolekcji

Niezależnie od tego, jak na to spojrzysz, o ile masz taką znormalizowaną relację, potrzebujesz dwóch zapytań, aby uzyskać wynik zawierający szczegóły z kolekcji „zadania” i wypełnienie szczegółami z kolekcji „projekty”. MongoDB w żaden sposób nie używa złączeń, podobnie jak mangusta. Mongoose oferuje .populate() , ale to tylko magia wygody dla tego, co zasadniczo polega na uruchamianiu innego zapytania i łączeniu wyników na wartości pola, do którego się odwołuje.

Jest to więc jeden przypadek, w którym być może ostatecznie rozważysz osadzenie informacji o projekcie w zadaniu. Oczywiście będzie duplikacja, ale to sprawia, że ​​wzorce zapytań są znacznie prostsze w przypadku pojedynczej kolekcji.

Utrzymując kolekcje oddzielone modelem odniesienia, zasadniczo masz dwa podejścia. Ale najpierw możesz użyć aggregate aby uzyskać wyniki bardziej zgodne z rzeczywistymi wymaganiami:

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

        }
    );

Używa tylko $group potok w celu akumulacji wartości „projectid” w kolekcji „tasks”. Aby policzyć wartości dla "completed" i "incomplete" używamy $cond operator, który jest trójargumentem decydującym, którą wartość przekazać do $sum . Ponieważ pierwszy warunek lub "if" jest tutaj wartością logiczną, wtedy wystarczy istniejące pole logiczne "complete", przekazując gdzie true do "then" lub "else" przekazując trzeci argument.

Te wyniki są poprawne, ale nie zawierają żadnych informacji z kolekcji „project” dla zebranych wartości „_id”. Jednym ze sposobów, aby dane wyjściowe wyglądały w ten sposób, jest wywołanie formy modelu .populate() z wnętrza wywołania zwrotnego wyników agregacji dla zwróconego obiektu „results”:

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

W tej formie .populate() call przyjmuje obiekt lub tablicę danych jako pierwszy argument, a drugi jest dokumentem opcji dla populacji, gdzie obowiązkowe pole to „ścieżka”. Spowoduje to przetworzenie wszystkich elementów i "wypełnienie" z modelu, który został wywołany, wstawiając te obiekty do danych wynikowych w wywołaniu zwrotnym.

Jako kompletny przykładowy wykaz:

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

A to da takie wyniki:

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

Więc .populate() działa dobrze w przypadku tego rodzaju wyników agregacji, nawet tak skutecznie, jak w przypadku innego zapytania, i ogólnie powinno być odpowiednie dla większości celów. Na liście znalazł się jednak konkretny przykład, w którym utworzono „dwa” projekty, ale oczywiście tylko „jedno” zadanie odnoszące się tylko do jednego z projektów.

Ponieważ agregacja pracuje nad kolekcją „zadań”, nie ma żadnej wiedzy na temat żadnego „projektu”, do którego nie ma odniesienia. Aby uzyskać pełną listę „projektów” z obliczonymi sumami, musisz bardziej szczegółowo uruchomić dwa zapytania i „scalić” wyniki.

Jest to w zasadzie „scalanie haszujące” różnych kluczy i danych, jednak dobrym pomocnikiem do tego jest moduł o nazwie nedb , co pozwala zastosować logikę w sposób bardziej spójny z zapytaniami i operacjami MongoDB.

Zasadniczo chcesz kopię danych z kolekcji „projekty” z polami rozszerzonymi, a następnie chcesz „scalić” lub .update() te informacje z wynikami agregacji. Ponownie jako kompletna lista, aby zademonstrować:

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

A wyniki tutaj:

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

Zawiera on dane z każdego „projektu” i zawiera obliczone wartości z powiązanej z nim kolekcji „zadań”.

Jest więc kilka podejść, które możesz zrobić. Ponownie, ostatecznie najlepiej będzie po prostu osadzić „zadania” w elementach „projektu”, co znowu byłoby prostym podejściem agregacyjnym. A jeśli zamierzasz osadzić informacje o zadaniu, możesz równie dobrze utrzymywać liczniki dla "zakończonych" i "niekompletnych" w obiekcie "projekt" i po prostu je aktualizować, gdy elementy są oznaczone jako ukończone w tablicy zadań za pomocą $inc operatora.

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

Musisz jednak znać aktualny stan zadania, aby aktualizacje liczników działały poprawnie, ale jest to łatwe do zakodowania i prawdopodobnie powinieneś mieć przynajmniej te szczegóły w obiekcie przechodzącym do twoich metod.

Osobiście przemodelowałbym do tej drugiej formy i to zrobiłem. Możesz wykonać zapytanie „scalanie”, jak pokazano na dwóch przykładach tutaj, ale oczywiście ma to swoją cenę.



  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Hash grup Ruby według wartości klucza

  2. Znajdź dokumenty w MongoDB, których pole tablicy jest podzbiorem tablicy zapytań

  3. Uwierzytelnianie Java/Mongodb

  4. Agregacja mongoDB zwraca puste

  5. Wyjątek:nie można przekonwertować z typu BSON EOO na Date