sql >> Database teknologi >  >> NoSQL >> MongoDB

Mongodb samlet sortering og begrænsning inden for gruppen

Det grundlæggende problem

Det er ikke den klogeste idé derude at forsøge at gøre dette i den nuværende sammenlægningsramme i en overskuelig nær fremtid. Hovedproblemet kommer naturligvis fra denne linje i den kode, du allerede har:

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

Og det betyder præcis det, i og med at det, der grundlæggende skal ske, er, at alle objekter inden for grupperingsnøglen skal skubbes ind i et array for at komme til "øverste N"-resultater i enhver senere kode.

Dette skalerer tydeligvis ikke, da størrelsen af ​​det array i sig selv meget tænkeligt kan overskride BSON-grænsen på 16 MB, og uanset resten af ​​dataene i det grupperede dokument. Den vigtigste fangst her er, at det ikke er muligt at "begrænse push" til kun et bestemt antal elementer. Der er et langvarigt JIRA-problem om netop sådan noget.

Alene af den grund er den mest praktiske tilgang til dette at køre individuelle forespørgsler for de "øverste N" elementer for hver grupperingsnøgle. Disse behøver ikke engang at være .aggregate() erklæringer (afhængigt af dataene) og kan virkelig være hvad som helst, der blot begrænser de "øverste N" værdier, du ønsker.

Bedste tilgang

Din arkitektur ser ud til at være på node.js med mongoose , men alt, der understøtter asynkron IO og parallel udførelse af forespørgsler, vil være den bedste mulighed. Ideelt set noget med dets eget API-bibliotek, der understøtter at kombinere resultaterne af disse forespørgsler til et enkelt svar.

For eksempel er der denne forenklede eksempelliste, der bruger din arkitektur og tilgængelige biblioteker (især async ), der gør disse parallelle og kombinerede resultater nøjagtigt:

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

Dette resulterer i kun de øverste 2 resultater for hver købmand i outputtet:

[
  {
    "_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
  }
]

Det er virkelig den mest effektive måde at behandle dette på, selvom det vil kræve ressourcer, da det stadig er flere forespørgsler. Men ikke i nærheden af ​​de ressourcer, der tæres op i aggregeringspipelinen, hvis du forsøger at gemme alle dokumenter i et array og behandle det.

Det samlede problem, nu og i nær fremtid

Til den linje er det muligt i betragtning af, at antallet af dokumenter ikke forårsager et brud på BSON-grænsen, at dette kan lade sig gøre. Metoder med den nuværende udgivelse af MongoDB er ikke gode til dette, men den kommende udgivelse (i skrivende stund gør 3.1.8 dev branche dette) introducerer i det mindste en $slice operatør til aggregeringsrørledningen. Så hvis du er klogere på aggregeringsoperationen og bruger en $sort først, så kan de allerede sorterede elementer i arrayet nemt udvælges:

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

Hvilket giver det samme grundlæggende resultat, da de øverste 2 emner "skåres" fra arrayet, når de først blev sorteret.

Det er faktisk også "muligt" i nuværende udgivelser, men med de samme grundlæggende begrænsninger, idet dette stadig involverer at skubbe alt indhold ind i et array efter at have sorteret indholdet først. Det kræver bare en "iterativ" tilgang. Du kan kode dette ud for at producere aggregeringspipelinen for større poster, men blot at vise "to" skulle vise, at det ikke er en rigtig god idé at prøve:

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

Og igen mens det er "muligt" i tidligere versioner (dette bruger 2.6 introducerede funktioner til at forkorte, da du allerede tagger $$ROOT ), er de grundlæggende trin at gemme arrayet og derefter få hvert element "af stakken" ved hjælp af $first og sammenligne det (og potentielt andre) med elementer i arrayet for at fjerne dem og derefter få det "næste første" element fra den stak, indtil dit "øverste N" til sidst er færdigt.

Konklusion

Indtil den dag kommer, at der er en sådan operation, der tillader elementerne i et $push aggregeringsakkumulator skal begrænses til et bestemt antal, så er dette ikke rigtig en praktisk operation for aggregering.

Du kan gøre det, hvis de data, du har i disse resultater, er små nok, og det kan endda bare være mere effektivt end behandlingen på klientsiden, hvis databaseserverne har tilstrækkelige specifikationer til at give en reel fordel. Men chancerne er, at ingen af ​​dem vil være tilfældet i de fleste rigtige applikationer med rimelig brug.

Det bedste bud er at bruge indstillingen "parallel forespørgsel", der vises først. Det vil altid skaleres godt, og der er ingen grund til at "kode omkring" en sådan logik, at en bestemt gruppering måske ikke returnerer i det mindste de samlede "øverste N" elementer, der kræves, og finder ud af, hvordan man beholder dem (meget længere eksempel på det udeladte ), da den blot udfører hver forespørgsel og kombinerer resultaterne.

Brug parallelle forespørgsler. Det vil være bedre end den kodede tilgang, du har, og den vil overgå den aggregeringstilgang, som langt hen ad vejen er vist. Indtil der er en bedre mulighed i det mindste.



  1. mongoose/mongodb forespørgsel på flere sorter

  2. Opretter forbindelse til MongoDB 3.0 med Java Spring

  3. Begræns listens længde i redis

  4. Sådan installeres Apache CouchDB 2.3.0 i Linux