mongodb中带有标签的文档:获取标签计数

时间:2015-04-11 18:50:36

标签: mongodb count tags

我在MongoDB中有一个collection1个包含标签的文档。标签是一个嵌入的字符串数组:

{
    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计算出新的/删除的标签?

2 个答案:

答案 0 :(得分:1)

正如昵称正确陈述的那样,解决方案将是一个聚合。虽然我会用一个nack来做:我们将其结果保存在一个集合中。我们将做的是交易实时结果以实现极速提升。

我该怎么做

通常,对实时结果的需求被高估了。因此,我会使用预先计算的标签统计数据,并每隔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})

好的,这是交易。首先,我们展开标签数组,按单个标签对其进行分组,并为相应标签的每次出现增加分数。接下来,lastRun集合中的upsert tagStats,我们可以做到,因为MongoDB是无模式的。接下来,我们创建一个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毫秒)时,它将返回一条记录。当然,您可以根据需要进行调整。

现在,我们可以把它包起来:

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

替代方法

如果实时结果 真的 很重要,并且您需要所有标记的统计信息,只需使用聚合而不将其保存到其他集合:

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

答案 1 :(得分:0)

回答你的问题:

  • B):您不必自己计算差异use $addToSet
  • A):您可以通过汇总框架获取计数,其中包含$unwind和$ count
  • 的组合