按值和条件分组
Group by values and conditions
对 mongo 很陌生,在解决这个问题时遇到了一些困难。我 collection 看起来像这样
{ "_id" : "PN89dNYYkBBmab3uH", "card_id" : 1, "vote" : 1, "time" : 1437700845154 }
{ "_id" : "Ldz7N5syeW2SXtQzP", "card_id" : 1, "vote" : 1, "time" : 1437700846035 }
{ "_id" : "v3XWHHvFSHwYxxk6H", "card_id" : 1, "vote" : 2, "time" : 1437700849817 }
{ "_id" : "eehcDaCyTdz6Yd2a9", "card_id" : 2, "vote" : 1, "time" : 1437700850666 }
{ "_id" : "efhcDaCyTdz6Yd2b9", "card_id" : 2, "vote" : 1, "time" : 1437700850666 }
{ "_id" : "efhcDaCyTdz7Yd2b9", "card_id" : 3, "vote" : 1, "time" : 1437700850666 }
{ "_id" : "w3XWgHvFSHwYxxk6H", "card_id" : 1, "vote" : 1, "time" : 1437700849817 }
我需要编写一个查询,基本上按投票对卡片进行分组(1 表示喜欢;2 表示不喜欢)。所以我需要得到每张卡片的喜欢减去不喜欢的总数,然后才能对它们进行排名。
结果如下:
card_id 1: 3 个喜欢 - 1 个不喜欢 = 2
card_id 3: 1 喜欢 - 0 不喜欢 = 1
card_id 2:2 个喜欢 - 0 个不喜欢 = 0
知道如何计算所有卡片的 1 票减 2 票然后对结果排序吗?
使用 meteorhacks:aggregate 包。
Cards = new Mongo.Collection('cards');
var groupedCards = Cards.aggregate({ $group: { _id: { card_id: '$card_id' }}});
groupedCards.forEach(function(card) {
console.log(card);
});
{ "_id" : "v3XWHHvFSHwYxxk6H", "card_id" : 1, "vote" : 2, "time" : 1437700849817 }
{ "_id" : "eehcDaCyTdz6Yd2a9", "card_id" : 2, "vote" : 1, "time" : 1437700850666 }
{ "_id" : "efhcDaCyTdz7Yd2b9", "card_id" : 3, "vote" : 1, "time" : 1437700850666 }
要使用 MongoDB 查询执行任何类型的 "grouping",那么您希望能够使用 aggregation framework 或 mapReduce。聚合框架通常是首选,因为它使用本机编码运算符而不是 JavaScript 转换,因此通常速度更快。
聚合语句只能在服务器 运行 端 API ,这确实有意义,因为您不想在客户端执行此操作。但它可以在那里完成并将结果提供给客户。
归因于 this answer 提供发布结果的方法:
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);
}
)
);
});
一般的聚合语句就一个$group
operation on the single key of "card_id". In order to get the "likes" and "dislikes" you use a "conditional expression" which is $cond
.
这是一个 "ternary" 运算符,它考虑对 "vote" 的值进行逻辑测试,如果它与预期类型匹配,则返回正数 1
,否则为 0
.
然后将这些值发送到累加器 $sum
以将它们加在一起,并通过 "like" 或 "dislike" 产生每个 "card_id" 的总计数.
对于 "total",最有效的方法是在执行分组。有一个 $add
运算符,但在这种情况下,它的使用将需要另一个管道阶段。所以我们只是在一个阶段上做。
最后有 $sort
顺序 "descending",因此最大的赞成票数排在最前面。这是可选的,您可能只想使用动态排序客户端。但对于消除必须这样做的开销的默认设置来说,这是一个好的开始。
这就是进行条件聚合并处理结果。
测试列表
这是我用新创建的流星项目测试的,没有插件,只有一个模板和 javascript 文件
控制台命令
meteor create cardtest
cd cardtest
meteor remove autopublish
使用问题中发布的文档在数据库中创建了 "cards" 集合。然后使用以下内容编辑默认文件:
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>
最终合集内容:
[
{
"_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
}
]
对 mongo 很陌生,在解决这个问题时遇到了一些困难。我 collection 看起来像这样
{ "_id" : "PN89dNYYkBBmab3uH", "card_id" : 1, "vote" : 1, "time" : 1437700845154 }
{ "_id" : "Ldz7N5syeW2SXtQzP", "card_id" : 1, "vote" : 1, "time" : 1437700846035 }
{ "_id" : "v3XWHHvFSHwYxxk6H", "card_id" : 1, "vote" : 2, "time" : 1437700849817 }
{ "_id" : "eehcDaCyTdz6Yd2a9", "card_id" : 2, "vote" : 1, "time" : 1437700850666 }
{ "_id" : "efhcDaCyTdz6Yd2b9", "card_id" : 2, "vote" : 1, "time" : 1437700850666 }
{ "_id" : "efhcDaCyTdz7Yd2b9", "card_id" : 3, "vote" : 1, "time" : 1437700850666 }
{ "_id" : "w3XWgHvFSHwYxxk6H", "card_id" : 1, "vote" : 1, "time" : 1437700849817 }
我需要编写一个查询,基本上按投票对卡片进行分组(1 表示喜欢;2 表示不喜欢)。所以我需要得到每张卡片的喜欢减去不喜欢的总数,然后才能对它们进行排名。
结果如下:
card_id 1: 3 个喜欢 - 1 个不喜欢 = 2
card_id 3: 1 喜欢 - 0 不喜欢 = 1
card_id 2:2 个喜欢 - 0 个不喜欢 = 0
知道如何计算所有卡片的 1 票减 2 票然后对结果排序吗?
使用 meteorhacks:aggregate 包。
Cards = new Mongo.Collection('cards');
var groupedCards = Cards.aggregate({ $group: { _id: { card_id: '$card_id' }}});
groupedCards.forEach(function(card) {
console.log(card);
});
{ "_id" : "v3XWHHvFSHwYxxk6H", "card_id" : 1, "vote" : 2, "time" : 1437700849817 }
{ "_id" : "eehcDaCyTdz6Yd2a9", "card_id" : 2, "vote" : 1, "time" : 1437700850666 }
{ "_id" : "efhcDaCyTdz7Yd2b9", "card_id" : 3, "vote" : 1, "time" : 1437700850666 }
要使用 MongoDB 查询执行任何类型的 "grouping",那么您希望能够使用 aggregation framework 或 mapReduce。聚合框架通常是首选,因为它使用本机编码运算符而不是 JavaScript 转换,因此通常速度更快。
聚合语句只能在服务器 运行 端 API ,这确实有意义,因为您不想在客户端执行此操作。但它可以在那里完成并将结果提供给客户。
归因于 this answer 提供发布结果的方法:
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);
}
)
);
});
一般的聚合语句就一个$group
operation on the single key of "card_id". In order to get the "likes" and "dislikes" you use a "conditional expression" which is $cond
.
这是一个 "ternary" 运算符,它考虑对 "vote" 的值进行逻辑测试,如果它与预期类型匹配,则返回正数 1
,否则为 0
.
然后将这些值发送到累加器 $sum
以将它们加在一起,并通过 "like" 或 "dislike" 产生每个 "card_id" 的总计数.
对于 "total",最有效的方法是在执行分组。有一个 $add
运算符,但在这种情况下,它的使用将需要另一个管道阶段。所以我们只是在一个阶段上做。
最后有 $sort
顺序 "descending",因此最大的赞成票数排在最前面。这是可选的,您可能只想使用动态排序客户端。但对于消除必须这样做的开销的默认设置来说,这是一个好的开始。
这就是进行条件聚合并处理结果。
测试列表
这是我用新创建的流星项目测试的,没有插件,只有一个模板和 javascript 文件
控制台命令
meteor create cardtest
cd cardtest
meteor remove autopublish
使用问题中发布的文档在数据库中创建了 "cards" 集合。然后使用以下内容编辑默认文件:
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>
最终合集内容:
[
{
"_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
}
]