Untuk melakukan "pengelompokan" apa pun dengan kueri MongoDB maka Anda ingin dapat menggunakan kerangka kerja agregasi atau mapReduce. Kerangka agregasi umumnya lebih disukai karena menggunakan operator kode asli daripada terjemahan JavaScript, dan karena itu biasanya lebih cepat.
Pernyataan agregasi hanya dapat dijalankan di sisi API server, yang masuk akal karena Anda tidak ingin melakukan ini pada klien. Tapi itu bisa dilakukan di sana dan membuat hasilnya tersedia untuk klien.
Dengan atribusi ke jawaban ini untuk menyediakan metode untuk mempublikasikan hasil:
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);
}
)
);
});
Pernyataan agregasi umum hanya ada $group
operasi pada satu kunci "card_id". Untuk mendapatkan "suka" dan "tidak suka" Anda menggunakan "ekspresi bersyarat" yaitu $cond
.
Ini adalah operator "ternary" yang mempertimbangkan tes logis pada nilai "vote", dan jika cocok dengan tipe yang diharapkan maka 1
positif dikembalikan, jika tidak maka 0
.
Nilai tersebut kemudian dikirim ke akumulator yaitu $sum
untuk menambahkannya bersama-sama, dan menghasilkan jumlah total untuk setiap "card_id" dengan "suka" atau "tidak suka".
Untuk "total", cara yang paling efisien adalah dengan mengaitkan nilai "positif" untuk "suka" dan nilai negatif untuk "tidak suka" pada saat yang sama saat melakukan pengelompokan. Ada $add
operator, tetapi dalam hal ini penggunaannya akan membutuhkan tahap pipa lain. Jadi kami hanya melakukannya di satu panggung saja.
Di akhir ini ada $sort
dalam urutan "menurun" sehingga penghitungan suara positif terbesar berada di atas. Ini opsional dan Anda mungkin hanya ingin menggunakan sisi klien penyortiran dinamis. Tapi ini adalah awal yang baik untuk default yang menghilangkan overhead karena harus melakukan itu.
Jadi itu adalah melakukan agregasi bersyarat dan bekerja dengan hasilnya.
Daftar uji
Inilah yang saya uji dengan proyek meteor yang baru dibuat, tanpa tambahan dan hanya satu templat dan file javascript
perintah konsol
meteor create cardtest
cd cardtest
meteor remove autopublish
Membuat koleksi "kartu" dalam database dengan dokumen yang diposting dalam pertanyaan. Kemudian edit file default dengan isi di bawah ini:
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>
Konten koleksi gabungan akhir:
[
{
"_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
}
]