MongoDB
 sql >> база данни >  >> NoSQL >> MongoDB

Mongodb агрегатно сортиране и ограничение в рамките на групата

Основният проблем

Не е най-мъдрата идея да се опитате да направите това в рамката за агрегиране в момента и в обозримо близко бъдеще. Основният проблем, разбира се, идва от този ред в кода, който вече имате:

"items" : { "$push": "$$ROOT" }

И това означава точно това, тъй като това, което по същество трябва да се случи, е, че всички обекти в ключа за групиране трябва да бъдат натиснати в масив, за да се стигне до „горните N“ резултати във всеки по-късен код.

Това очевидно не се мащабира, тъй като в крайна сметка размерът на самия този масив може да надхвърли ограничението на BSON от 16MB и независимо от останалите данни в групирания документ. Основната уловка тук е, че не е възможно да се „ограничи натискането“ само до определен брой елементи. Има дългогодишен проблем с JIRA точно за такова нещо.

Само поради тази причина най-практичният подход към това е да се изпълняват индивидуални заявки за „най-добрите N“ елементи за всеки групиращ ключ. Те дори не трябва да са .aggregate() оператори (в зависимост от данните) и наистина може да бъде всичко, което просто ограничава желаните от вас „най-големи N“ стойности.

Най-добър подход

Архитектурата ви изглежда е на node.js с mongoose , но всичко, което поддържа асинхронен IO и паралелно изпълнение на заявки, ще бъде най-добрият вариант. В идеалния случай нещо със собствена API библиотека, която поддържа комбиниране на резултатите от тези заявки в един отговор.

Например има този опростен примерен списък, използващ вашата архитектура и налични библиотеки (по-специално async ), което прави тези паралелни и комбинирани резултати точно:

var async = require('async'),
    mongoose = require('mongoose'),
    Schema = mongoose.Schema;

mongoose.connect('mongodb://localhost/test');

var data = [
  { "merchant": 1, "rating": 1 },
  { "merchant": 1, "rating": 2 },
  { "merchant": 1, "rating": 3 },
  { "merchant": 2, "rating": 1 },
  { "merchant": 2, "rating": 2 },
  { "merchant": 2, "rating": 3 }
];

var testSchema = new Schema({
  merchant: Number,
  rating: Number
});

var Test = mongoose.model( 'Test', testSchema, 'test' );

async.series(
  [
    function(callback) {
      Test.remove({},callback);
    },
    function(callback) {
      async.each(data,function(item,callback) {
        Test.create(item,callback);
      },callback);
    },
    function(callback) {
      async.waterfall(
        [
          function(callback) {
            Test.distinct("merchant",callback);
          },
          function(merchants,callback) {
            async.concat(
              merchants,
              function(merchant,callback) {
                Test.find({ "merchant": merchant })
                  .sort({ "rating": -1 })
                  .limit(2)
                  .exec(callback);
              },
              function(err,results) {
                console.log(JSON.stringify(results,undefined,2));
                callback(err);
              }
            );
          }
        ],
        callback
      );
    }
  ],
  function(err) {
    if (err) throw err;
    mongoose.disconnect();
  }
);

Това води до само първите 2 резултата за всеки търговец в изхода:

[
  {
    "_id": "560d153669fab495071553ce",
    "merchant": 1,
    "rating": 3,
    "__v": 0
  },
  {
    "_id": "560d153669fab495071553cd",
    "merchant": 1,
    "rating": 2,
    "__v": 0
  },
  {
    "_id": "560d153669fab495071553d1",
    "merchant": 2,
    "rating": 3,
    "__v": 0
  },
  {
    "_id": "560d153669fab495071553d0",
    "merchant": 2,
    "rating": 2,
    "__v": 0
  }
]

Това наистина е най-ефективният начин за обработка на това, въпреки че ще отнеме ресурси, тъй като все още има множество заявки. Но не са близо до ресурсите, изядени в конвейера за агрегация, ако се опитате да съхраните всички документи в масив и да го обработите.

Общият проблем, сега и близко бъдеще

В този ред е възможно, като се има предвид, че броят на документите не причинява нарушение на лимита на BSON, това може да бъде направено. Методите с текущата версия на MongoDB не са страхотни за това, но предстоящата версия (към писането клонът на dev 3.1.8 прави това) поне въвежда $slice оператор към тръбопровода за агрегация. Така че, ако сте по-умни относно операцията за агрегиране и използвайте $sort първо, след това вече сортираните елементи в масива могат лесно да бъдат избрани:

var async = require('async'),
    mongoose = require('mongoose'),
    Schema = mongoose.Schema;

mongoose.connect('mongodb://localhost/test');

var data = [
  { "merchant": 1, "rating": 1 },
  { "merchant": 1, "rating": 2 },
  { "merchant": 1, "rating": 3 },
  { "merchant": 2, "rating": 1 },
  { "merchant": 2, "rating": 2 },
  { "merchant": 2, "rating": 3 }
];

var testSchema = new Schema({
  merchant: Number,
  rating: Number
});

var Test = mongoose.model( 'Test', testSchema, 'test' );

async.series(
  [
    function(callback) {
      Test.remove({},callback);
    },
    function(callback) {
      async.each(data,function(item,callback) {
        Test.create(item,callback);
      },callback);
    },
    function(callback) {
      Test.aggregate(
        [
          { "$sort": { "merchant": 1, "rating": -1 } },
          { "$group": {
            "_id": "$merchant",
            "items": { "$push": "$$ROOT" }
          }},
          { "$project": {
            "items": { "$slice": [ "$items", 2 ] }
          }}
        ],
        function(err,results) {
          console.log(JSON.stringify(results,undefined,2));
          callback(err);
        }
      );
    }
  ],
  function(err) {
    if (err) throw err;
    mongoose.disconnect();
  }
);

Което дава същия основен резултат, тъй като първите 2 елемента се „изрязват“ от масива, след като са сортирани първи.

Това също всъщност е "възможно" в текущите издания, но със същите основни ограничения, тъй като това все още включва избутване на цялото съдържание в масив след първо сортиране на съдържанието. Просто е необходим "итеративен" подход. Можете да кодирате това, за да създадете тръбопровода за агрегиране за по-големи вписвания, но само показването на „две“ трябва да покаже, че не е наистина страхотна идея да опитате:

var async = require('async'),
    mongoose = require('mongoose'),
    Schema = mongoose.Schema;

mongoose.connect('mongodb://localhost/test');

var data = [
  { "merchant": 1, "rating": 1 },
  { "merchant": 1, "rating": 2 },
  { "merchant": 1, "rating": 3 },
  { "merchant": 2, "rating": 1 },
  { "merchant": 2, "rating": 2 },
  { "merchant": 2, "rating": 3 }
];

var testSchema = new Schema({
  merchant: Number,
  rating: Number
});

var Test = mongoose.model( 'Test', testSchema, 'test' );

async.series(
  [
    function(callback) {
      Test.remove({},callback);
    },
    function(callback) {
      async.each(data,function(item,callback) {
        Test.create(item,callback);
      },callback);
    },
    function(callback) {
      Test.aggregate(
        [
          { "$sort": { "merchant": 1, "rating": -1 } },
          { "$group": {
            "_id": "$merchant",
            "items": { "$push": "$$ROOT" }
          }},
          { "$unwind": "$items" },
          { "$group": {
            "_id": "$_id",
            "first": { "$first": "$items" },
            "items": { "$push": "$items" }
          }},
          { "$unwind": "$items" },
          { "$redact": {
            "$cond": [
              { "$eq": [ "$items", "$first" ] },
              "$$PRUNE",
              "$$KEEP"
            ]
          }},
          { "$group": {
            "_id": "$_id",
            "first": { "$first": "$first" },
            "second": { "$first": "$items" }
          }},
          { "$project": {
            "items": {
              "$map": {
                "input": ["A","B"],
                "as": "el",
                "in": {
                  "$cond": [
                    { "$eq": [ "$$el", "A" ] },
                    "$first",
                    "$second"
                  ]
                }
              }
            }
          }}
        ],
        function(err,results) {
          console.log(JSON.stringify(results,undefined,2));
          callback(err);
        }
      );
    }
  ],
  function(err) {
    if (err) throw err;
    mongoose.disconnect();
  }
);

И отново, докато е „възможно“ в по-ранните версии (това използва въведените от 2.6 функции за съкращаване, тъй като вече сте маркирали $$ROOT ), основните стъпки са съхраняване на масива и след това извеждане на всеки елемент „от стека“ с помощта на $first и сравняване на това (и потенциално други) с елементи в масива, за да ги премахнете и след това да извадите елемента „следващият първи“ от този стек, докато най-накрая вашето „топ N“ бъде готово.

Заключение

Докато не дойде денят, в който има такава операция, която позволява на елементите в $push акумулаторът за агрегация да бъде ограничен до определен брой, тогава това всъщност не е практична операция за агрегат.

Можете да го направите, ако данните, които имате в тези резултати, са достатъчно малки и може дори да са по-ефективни от обработката от страна на клиента, ако сървърите на базата данни са с достатъчна спецификация, за да осигурят реално предимство. Но има вероятност нито едното, нито другото да е така в повечето реални приложения с разумна употреба.

Най-добрият залог е да използвате опцията "паралелна заявка", демонстрирана първа. Винаги ще се мащабира добре и няма нужда да се "кодира" такава логика, че определено групиране може да не върне поне общите необходими "най-големи N" елементи и да разбере как да ги запази (много по-дълъг пример за това, което е пропуснато ), тъй като просто изпълнява всяка заявка и комбинира резултатите.

Използвайте паралелни заявки. Той ще бъде по-добър от кодирания подход, който имате, и ще надмине демонстрирания дълъг път подхода за агрегиране. Докато има поне по-добър вариант.



  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. MongoDB $милисекунда

  2. Използване на PouchDB с MongoDB

  3. Комбинирайте две заявки ИЛИ с И в Mongoose

  4. MongoDB findAndModify()

  5. как да $project ObjectId към низова стойност в mongodb агрегат?