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

Raggruppa per valori e condizioni

Per eseguire qualsiasi tipo di "raggruppamento" con le query MongoDB, devi essere in grado di utilizzare il framework di aggregazione o mapReduce. Il framework di aggregazione è generalmente preferito in quanto utilizza operatori codificati nativi anziché la traduzione JavaScript ed è quindi in genere più veloce.

Le istruzioni di aggregazione possono essere eseguite solo sul lato API del server, il che ha senso perché non si desidera eseguire questa operazione sul client. Ma può essere fatto lì e rendere i risultati disponibili al cliente.

Con attribuzione a questa risposta per fornire i metodi per pubblicare i risultati:

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

});

L'istruzione generale di aggregazione è solo un $group operazione sulla chiave singola di "card_id". Per ottenere i "mi piace" e i "non mi piace" usi un'"espressione condizionale" che è $cond .

Questo è un operatore "ternario" che considera un test logico sul valore di "vote", e dove corrisponde al tipo Expect quindi un 1 positivo viene restituito, altrimenti è 0 .

Questi valori vengono quindi inviati all'accumulatore che è $sum per sommarli e produrre i conteggi totali per ogni "card_id" con "mi piace" o "non mi piace".

Per il "totale", il modo più efficiente è attribuire un valore "positivo" per "mi piace" e un valore negativo per "non mi piace" contemporaneamente al raggruppamento. C'è un $add operatore, ma in questo caso il suo utilizzo richiederebbe un'altra fase della pipeline. Quindi lo facciamo semplicemente su un unico stadio.

Alla fine di questo c'è un $sort in ordine "decrescente", quindi il maggior numero di voti positivi è in cima. Questo è facoltativo e potresti semplicemente voler utilizzare l'ordinamento dinamico lato client. Ma è un buon inizio per un'impostazione predefinita che rimuove il sovraccarico di doverlo fare.

Quindi questo sta facendo un'aggregazione condizionale e lavorando con i risultati.

Elenco di prova

Questo è ciò che ho testato con il progetto meteor appena creato, senza componenti aggiuntivi e solo un singolo modello e file javascript

comandi della console

meteor create cardtest
cd cardtest
meteor remove autopublish

Creata la raccolta "carte" nel database con i documenti inseriti nella domanda. E poi ho modificato i file predefiniti con i contenuti seguenti:

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

  });
}

testcarte.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>

Contenuti aggregati finali della raccolta:

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