mongodb 中带有标签的文档:获取标签计数
documents with tags in mongodb: getting tag counts
我有 collection1
个带有 MongoDB 标签的文档。标签是嵌入的字符串数组:
{
name: 'someObj',
tags: ['tag1', 'tag2', ...]
}
我想知道集合中每个标签的数量。因此我有另一个 collection2
标签数:
{
{
tag: 'tag1',
score: 2
}
{
tag: 'tag2',
score: 10
}
}
现在我必须让两者保持同步。在 collection1
中插入或从中删除时,这是相当微不足道的。但是,当我更新 collection1
时,我会执行以下操作:
1.) 获取旧文档
var oldObj = collection1.find({ _id: id });
2.) 计算新旧标签数组的差异
var removedTags = $(oldObj.tags).not(obj.tags).get();
var insertedTags = $(obj.tags).not(oldObj.tags).get();
3.) 更新旧文档
collection1.update(
{ _id: id },
{ $set: obj }
);
4.) 更新插入和删除标签的分数
// increment score of each inserted tag
insertedTags.forEach(function(val, idx) {
// $inc will set score = 1 on insert
collection2.update(
{ tag: val },
{ $inc: { score: 1 } },
{ upsert: true }
)
});
// decrement score of each removed tag
removedTags.forEach(function(val, idx) {
// $inc will set score = -1 on insert
collection2.update(
{ tag: val },
{ $inc: { score: -1 } },
{ upsert: true }
)
});
我的问题:
A) 这种分别记录分数的方法是否有效?或者是否有更有效的一次性查询从 collection1 中获取分数?
B) 即使分开记账是更好的选择:可以用更少的步骤完成吗?让 mongoDB 计算哪些标签是新的/删除的?
回答您的问题:
- B): 你不必自己计算 dif use $addToSet
- A):您可以通过聚合框架结合 $unwind 和 $count
来获取计数
正如 nikmilion 正确指出的那样,解决方案是聚合。虽然我会用 nack 来做:我们将把它的结果保存在 collection 中。要做的是用实时结果换取极速提升。
我会怎么做
往往高估了对实时结果的需求。因此,我会使用预先计算好的标签统计数据,并每 5 分钟左右更新一次。这应该足够好了,因为大多数此类调用都是由客户端异步请求的,因此在必须针对特定请求进行计算的情况下,一些延迟可以忽略不计。
db.tags.aggregate(
{$unwind:"$tags"},
{$group: { _id:"$tags", score:{"$sum":1} } },
{$out:"tagStats"}
)
db.tagStats.update(
{'lastRun':{$exists:true}},
{'lastRun':new Date()},
{upsert:true}
)
db.tagStats.ensureIndex({lastRun:1}, {sparse:true})
好的,这是交易。首先,我们展开标签数组,将其按单个标签分组,并为每次出现的相应标签增加分数。接下来,我们 upsert lastRun
in the tagStats
collection, which we can do since MongoDB is schemaless. Next, we create a sparse index,它只保存索引字段存在的文档的值。如果索引已经存在,ensureIndex
是一个非常便宜的查询;然而,由于我们将在我们的代码中使用该查询,我们不需要手动创建索引。使用此过程,以下查询
db.tagStats.find(
{lastRun:{ $lte: new Date( ISODate().getTime() - 300000 ) } },
{_id:0, lastRun:1}
)
变成 covered query:从索引回答的查询,它往往驻留在 RAM 中,使该查询快如闪电(在我的测试中略低于 0.5 毫秒的中位数)。那么这个查询是做什么的呢?当聚合的最后一个 运行 是 运行 超过 5 分钟(5*60*1000 = 300000 毫秒)之前,它将 return 一条记录。当然,您可以根据自己的需要进行调整。
现在,我们可以结束了:
var hasToRun = db.tagStats.find(
{lastRun:{ $lte: new Date( ISODate().getTime() - 300000 ) } },
{_id:0, lastRun:1}
);
if(hasToRun){
db.tags.aggregate(
{$unwind:"$tags"},
{$group: {_id:"$tags", score:{"$sum":1} } },
{$out:"tagStats"}
)
db.tagStats.update(
{'lastRun':{$exists:true}},
{'lastRun':new Date()},
{upsert:true}
);
db.tagStats.ensureIndex({lastRun:1},{sparse:true});
}
// For all stats
var tagsStats = db.tagStats.find({score:{$exists:true}});
// score for a specific tag
var scoreForTag = db.tagStats.find({score:{$exists:true},_id:"tag1"});
替代方法
如果实时结果 真的 很重要并且您需要所有标签的统计信息,只需使用聚合而不将其保存到另一个collection:
db.tags.aggregate(
{$unwind:"$tags"},
{$group: { _id:"$tags", score:{"$sum":1} } },
)
如果您一次只需要一个特定标签的结果,实时方法可能是使用特殊索引,创建覆盖查询并简单地计算结果:
db.tags.ensureIndex({tags:1})
var numberOfOccurences = db.tags.find({tags:"tag1"},{_id:0,tags:1}).count();
我有 collection1
个带有 MongoDB 标签的文档。标签是嵌入的字符串数组:
{
name: 'someObj',
tags: ['tag1', 'tag2', ...]
}
我想知道集合中每个标签的数量。因此我有另一个 collection2
标签数:
{
{
tag: 'tag1',
score: 2
}
{
tag: 'tag2',
score: 10
}
}
现在我必须让两者保持同步。在 collection1
中插入或从中删除时,这是相当微不足道的。但是,当我更新 collection1
时,我会执行以下操作:
1.) 获取旧文档
var oldObj = collection1.find({ _id: id });
2.) 计算新旧标签数组的差异
var removedTags = $(oldObj.tags).not(obj.tags).get();
var insertedTags = $(obj.tags).not(oldObj.tags).get();
3.) 更新旧文档
collection1.update(
{ _id: id },
{ $set: obj }
);
4.) 更新插入和删除标签的分数
// increment score of each inserted tag
insertedTags.forEach(function(val, idx) {
// $inc will set score = 1 on insert
collection2.update(
{ tag: val },
{ $inc: { score: 1 } },
{ upsert: true }
)
});
// decrement score of each removed tag
removedTags.forEach(function(val, idx) {
// $inc will set score = -1 on insert
collection2.update(
{ tag: val },
{ $inc: { score: -1 } },
{ upsert: true }
)
});
我的问题:
A) 这种分别记录分数的方法是否有效?或者是否有更有效的一次性查询从 collection1 中获取分数?
B) 即使分开记账是更好的选择:可以用更少的步骤完成吗?让 mongoDB 计算哪些标签是新的/删除的?
回答您的问题:
- B): 你不必自己计算 dif use $addToSet
- A):您可以通过聚合框架结合 $unwind 和 $count 来获取计数
正如 nikmilion 正确指出的那样,解决方案是聚合。虽然我会用 nack 来做:我们将把它的结果保存在 collection 中。要做的是用实时结果换取极速提升。
我会怎么做
往往高估了对实时结果的需求。因此,我会使用预先计算好的标签统计数据,并每 5 分钟左右更新一次。这应该足够好了,因为大多数此类调用都是由客户端异步请求的,因此在必须针对特定请求进行计算的情况下,一些延迟可以忽略不计。
db.tags.aggregate(
{$unwind:"$tags"},
{$group: { _id:"$tags", score:{"$sum":1} } },
{$out:"tagStats"}
)
db.tagStats.update(
{'lastRun':{$exists:true}},
{'lastRun':new Date()},
{upsert:true}
)
db.tagStats.ensureIndex({lastRun:1}, {sparse:true})
好的,这是交易。首先,我们展开标签数组,将其按单个标签分组,并为每次出现的相应标签增加分数。接下来,我们 upsert lastRun
in the tagStats
collection, which we can do since MongoDB is schemaless. Next, we create a sparse index,它只保存索引字段存在的文档的值。如果索引已经存在,ensureIndex
是一个非常便宜的查询;然而,由于我们将在我们的代码中使用该查询,我们不需要手动创建索引。使用此过程,以下查询
db.tagStats.find(
{lastRun:{ $lte: new Date( ISODate().getTime() - 300000 ) } },
{_id:0, lastRun:1}
)
变成 covered query:从索引回答的查询,它往往驻留在 RAM 中,使该查询快如闪电(在我的测试中略低于 0.5 毫秒的中位数)。那么这个查询是做什么的呢?当聚合的最后一个 运行 是 运行 超过 5 分钟(5*60*1000 = 300000 毫秒)之前,它将 return 一条记录。当然,您可以根据自己的需要进行调整。
现在,我们可以结束了:
var hasToRun = db.tagStats.find(
{lastRun:{ $lte: new Date( ISODate().getTime() - 300000 ) } },
{_id:0, lastRun:1}
);
if(hasToRun){
db.tags.aggregate(
{$unwind:"$tags"},
{$group: {_id:"$tags", score:{"$sum":1} } },
{$out:"tagStats"}
)
db.tagStats.update(
{'lastRun':{$exists:true}},
{'lastRun':new Date()},
{upsert:true}
);
db.tagStats.ensureIndex({lastRun:1},{sparse:true});
}
// For all stats
var tagsStats = db.tagStats.find({score:{$exists:true}});
// score for a specific tag
var scoreForTag = db.tagStats.find({score:{$exists:true},_id:"tag1"});
替代方法
如果实时结果 真的 很重要并且您需要所有标签的统计信息,只需使用聚合而不将其保存到另一个collection:
db.tags.aggregate(
{$unwind:"$tags"},
{$group: { _id:"$tags", score:{"$sum":1} } },
)
如果您一次只需要一个特定标签的结果,实时方法可能是使用特殊索引,创建覆盖查询并简单地计算结果:
db.tags.ensureIndex({tags:1})
var numberOfOccurences = db.tags.find({tags:"tag1"},{_id:0,tags:1}).count();