MongoDB
 sql >> Datenbank >  >> NoSQL >> MongoDB

Gruppieren nach Werten und Bedingungen

Um irgendeine Art von "Gruppierung" mit MongoDB-Abfragen durchzuführen, möchten Sie das Aggregationsframework oder mapReduce verwenden können. Das Aggregations-Framework wird im Allgemeinen bevorzugt, da es native codierte Operatoren anstelle von JavaScript-Übersetzungen verwendet und daher in der Regel schneller ist.

Aggregationsanweisungen können nur auf der Server-API-Seite ausgeführt werden, was sinnvoll ist, da Sie dies nicht auf dem Client tun möchten. Aber es kann dort gemacht werden und die Ergebnisse dem Kunden zur Verfügung stellen.

Mit Zuschreibung an diese Antwort für die Bereitstellung der Methoden zum Veröffentlichen von Ergebnissen:

Meteor.publish("cardLikesDislikes", function(args) {
    var sub = this;

    var db = MongoInternals.defaultRemoteCollectionDriver().mongo.db;

    var pipeline = [
        { "$group": {
            "_id": "$card_id",
            "likes": {
                "$sum": {
                    "$cond": [
                        { "$eq": [ "$vote", 1 ] },
                        1,
                        0
                    ]
                }
            },
            "dislikes": {
                "$sum": {
                    "$cond": [
                        { "$eq": [ "$vote", 2 ] },
                        1,
                        0
                    ]
                }
            },
            "total": {
                "$sum": {
                    "$cond": [
                        { "$eq": [ "$vote", 1 ] },
                        1,
                        -1
                    ]
                }
            }
        }},
        { "$sort": { "total": -1 } }
    ];

    db.collection("server_collection_name").aggregate(        
        pipeline,
        // Need to wrap the callback so it gets called in a Fiber.
        Meteor.bindEnvironment(
            function(err, result) {
                // Add each of the results to the subscription.
                _.each(result, function(e) {
                    // Generate a random disposable id for aggregated documents
                    sub.added("client_collection_name", Random.id(), {
                        card: e._id,                        
                        likes: e.likes,
                        dislikes: e.dislikes,
                        total: e.total
                    });
                });
                sub.ready();
            },
            function(error) {
                Meteor._debug( "Error doing aggregation: " + error);
            }
        )
    );

});

Die allgemeine Aggregationsanweisung dort ist nur eine $group Operation auf dem einzelnen Schlüssel von "card_id". Um die "Mag ich" und "Mag ich nicht" zu erhalten, verwenden Sie einen "bedingten Ausdruck", der $cond ist .

Dies ist ein „ternärer“ Operator, der einen logischen Test auf den Wert von „vote“ betrachtet, und wo er mit dem Erwartungstyp übereinstimmt, dann ein positives 1 zurückgegeben, ansonsten 0 .

Diese Werte werden dann an den Akkumulator gesendet, der $sum ist um sie zusammenzuzählen und die Gesamtzahl für jede "card_id" entweder durch "like" oder "dislike" zu erzeugen.

Für das „Gesamt“ ist es am effizientesten, gleichzeitig mit der Gruppierung einen „positiven“ Wert für „Gefällt mir“ und einen negativen Wert für „Gefällt mir nicht“ zu vergeben. Es gibt ein $add Betreiber, aber in diesem Fall würde seine Verwendung eine weitere Pipeline-Phase erfordern. Also machen wir es stattdessen einfach auf einer einzigen Bühne.

Am Ende steht ein $sort in "absteigender" Reihenfolge, sodass die größten positiven Stimmenzahlen oben stehen. Dies ist optional und Sie möchten möglicherweise nur die dynamische Sortierung auf Clientseite verwenden. Aber es ist ein guter Anfang für eine Standardeinstellung, die den Aufwand dafür beseitigt.

Das bedeutet also, eine bedingte Aggregation durchzuführen und mit den Ergebnissen zu arbeiten.

Testliste

Dies habe ich mit einem neu erstellten Meteor-Projekt getestet, ohne Add-Ins und nur mit einer einzigen Vorlage und Javascript-Datei

Konsolenbefehle

meteor create cardtest
cd cardtest
meteor remove autopublish

Erstellung der „Karten“-Sammlung in der Datenbank mit den in der Frage geposteten Dokumenten. Und bearbeitete dann die Standarddateien mit den folgenden Inhalten:

cardtest.js

Cards = new Meteor.Collection("cardStore");

if (Meteor.isClient) {

  Meteor.subscribe("cards");

  Template.body.helpers({
    cards: function() {
      return Cards.find({});
    }
  });

}

if (Meteor.isServer) {

  Meteor.publish("cards",function(args) {
    var sub = this;

    var db = MongoInternals.defaultRemoteCollectionDriver().mongo.db;

    var pipeline = [
      { "$group": {
        "_id": "$card_id",
        "likes": { "$sum": { "$cond": [{ "$eq": [ "$vote", 1 ] },1,0] } },
        "dislikes": { "$sum": { "$cond": [{ "$eq": [ "$vote", 2 ] },1,0] } },
        "total": { "$sum": { "$cond": [{ "$eq": [ "$vote", 1 ] },1,-1] } }
      }},
      { "$sort": { "total": -1, "_id": 1 } }

    ];

    db.collection("cards").aggregate(
      pipeline,
      Meteor.bindEnvironment(
        function(err,result) {
          _.each(result,function(e) {
            e.card_id = e._id;
            delete e._id;

            sub.added("cardStore",Random.id(), e);
          });
          sub.ready();
        },
        function(error) {
          Meteor._debug( "error running: " + error);
        }
      )
    );

  });
}

cardtest.html

<head>
  <title>cardtest</title>
</head>

<body>
  <h1>Card aggregation</h1>

  <table border="1">
    <tr>
      <th>Card_id</th>
      <th>Likes</th>
      <th>Dislikes</th>
      <th>Total</th>
    </tr>
    {{#each cards}}
      {{> card }}
    {{/each}}
  </table>

</body>

<template name="card">
  <tr>
    <td>{{card_id}}</td>
    <td>{{likes}}</td>
    <td>{{dislikes}}</td>
    <td>{{total}}</td>
  </tr>
</template>

Endgültiger aggregierter Sammlungsinhalt:

[
   {
     "_id":"Z9cg2p2vQExmCRLoM",
     "likes":3,
     "dislikes":1,
     "total":2,
     "card_id":1
   },
   {
     "_id":"KQWCS8pHHYEbiwzBA",
      "likes":2,
      "dislikes":0,
      "total":2,
      "card_id":2
   },
   {
      "_id":"KbGnfh3Lqcmjow3WN",
      "likes":1,
      "dislikes":0,
      "total":1,
      "card_id":3
   }
]