sql >> Base de Datos >  >> NoSQL >> MongoDB

Agrupar por valores y condiciones

Para hacer cualquier tipo de "agrupación" con consultas de MongoDB, entonces querrá poder usar el marco de agregación o mapReduce. En general, se prefiere el marco de agregación, ya que utiliza operadores codificados nativos en lugar de la traducción de JavaScript y, por lo tanto, suele ser más rápido.

Las declaraciones de agregación solo se pueden ejecutar en el lado de la API del servidor, lo que tiene sentido porque no querrá hacer esto en el cliente. Pero se puede hacer allí y poner los resultados a disposición del cliente.

Con atribución a esta respuesta por proporcionar los métodos para publicar resultados:

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

});

La declaración general de agregación es solo un $group operación en la clave única de "card_id". Para obtener los "me gusta" y los "no me gusta", utiliza una "expresión condicional" que es $cond .

Este es un operador "ternario" que considera una prueba lógica sobre el valor de "voto", y donde coincide con el tipo esperado, entonces un 1 positivo se devuelve, de lo contrario es 0 .

Esos valores luego se envían al acumulador que es $sum para sumarlas y producir los recuentos totales para cada "card_id" ya sea por "me gusta" o "no me gusta".

Para el "total", la forma más eficiente es atribuir un valor "positivo" para "me gusta" y un valor negativo para "no me gusta" al mismo tiempo que se realiza la agrupación. Hay un $add operador, pero en este caso su uso requeriría otra etapa de canalización. Así que lo hacemos en una sola etapa en su lugar.

Al final de esto hay un $sort en orden "descendente" para que los conteos de votos positivos más grandes estén en la parte superior. Esto es opcional y es posible que desee utilizar el lado del cliente de clasificación dinámica. Pero es un buen comienzo para un valor predeterminado que elimina la sobrecarga de tener que hacer eso.

Eso es hacer una agregación condicional y trabajar con los resultados.

Lista de prueba

Esto es lo que probé con un proyecto de meteoros recién creado, sin complementos y solo una plantilla y un archivo javascript

comandos de consola

meteor create cardtest
cd cardtest
meteor remove autopublish

Creó la colección de "tarjetas" en la base de datos con los documentos publicados en la pregunta. Y luego editó los archivos predeterminados con los siguientes contenidos:

prueba de tarjeta.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);
        }
      )
    );

  });
}

prueba de tarjeta.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>

Contenido agregado final de la colección:

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