用户评级的模式 - 密钥/值DB

时间:2014-11-13 17:05:12

标签: mongodb database-schema

我们正在使用MongoDB,并且我正在计算用于存储评级的模式。

  • 评级的值为1-5。
  • 我想存储其他值,例如fromUser

这很好,但我的主要问题是设置它,以便重新计算平均值尽可能高效。


解决方案1 ​​ - 单独的评级等级

首先想到的是创建一个单独的Ratings类,并在Ratings类中存储一个指向User的指针数组。我第二次猜到这一点的原因是,每次有新Ratings进入时我们都必须查询所有Rating个对象,以便我们可以重新计算平均值

...

解决方案2 - 用户类中的词典

第二个想法是直接在User类中存储一个字典,用于存储这些Ratings个对象。这比解决方案1稍微轻一点,但我们每次更新时都会重写每个用户的整个Ratings历史记录。这似乎很危险。

...

解决方案3 - 用户类别中具有单独平均值的单独评级类

我们在自己的类中有Ratings的混合选项,以及指向它们的指针数组,但是,我们在用户类中保留了两个值 - ratingsAveratingsCount。这样,当设置新的评级时,我们保存该对象,但我们可以轻松地重新计算ratingsAve


解决方案3听起来对我来说最好,但我只是想知道我们是否需要通过重新检查评级历史来重置ratingsAve以确保所有内容都检出来包含定期校准。 / p>

我可能会过度思考这一点,但我在数据库模式创建方面并不是那么出色,这似乎是一个标准的模式问题,我应该知道如何实现。

哪个是确保一致性以及重新计算效率的最佳选择?

4 个答案:

答案 0 :(得分:23)

首先,“用户类词典”不是一个好主意。为什么?添加额外费率对象需要将新项目推送到数组,这意味着将删除旧项目,此插入称为“移动文档”。移动文档很慢并且MongoDB在重用空白空间方面不是很好,因此移动大量文档会导致大量空数据文件(“MongoDB The Definitive Guide”一书中的一些文本)。

那么什么是正确的解决方案:假设您有一个名为Blogs的集合,并希望为您的博客帖子实施评级解决方案,并另外跟踪每个基于用户的费率操作。

博客文档的架构如下:

{
   _id : ....,
   title: ....,
   ....
   rateCount : 0,
   rateValue : 0,
   rateAverage: 0
}

您需要使用此文档架构的另一个集合(费率):

{
    _id: ....,
    userId: ....,
    postId:....,
    value: ..., //1 to 5
    date:....   
}

你需要为它定义一个合适的索引:

db.Rates.ensureIndex({userId : 1, postId : 1})// very useful. it will result in a much faster search operation in case you want to check if a user has rated the post previously

当用户想要评分时,首先需要检查用户是否对帖子进行了评分。假设用户是'user1',那么查询将是

var ratedBefore = db.Rates.find({userId : 'user1', postId : 'post1'}).count()

根据ratedBefore,如果 !ratedBefore ,则将新的费率文档插入到费率收集并更新博客状态,否则,不允许用户评分

if(!ratedBefore)
{
    var postId = 'post1'; // this id sould be passed before by client driver
    var userId = 'user1'; // this id sould be passed before by client driver
    var rateValue = 1; // to 5
    var rate = 
    {       
       userId: userId,
       postId: postId,
       value: rateValue,
       date:new Date()  
    };

    db.Rates.insert(rate);
    db.Blog.update({"_id" : postId}, {$inc : {'rateCount' : 1, 'rateValue' : rateValue}});
}

那么rateAverage会发生什么? 我强烈建议您在客户端根据rateCountrateValue进行计算,使用rateAverage轻松更新mongoquery,但您不应该这样做。为什么?简单的答案是:对于客户来说,这是一个非常容易的工作来处理这类工作,并且在每个博客文档上放置平均值都需要不必要的更新操作。

平均查询将计算为:

var blog = db.Blog.findOne({"_id" : "post1"});
var avg = blog.rateValue / blog.rateCount;
print(avg);

使用这种方法,您将获得mongodb的最高性能,并且您可以根据用户,帖子和日期跟踪每个费率。

答案 1 :(得分:2)

我会做的有点不同:拥有一个User类和一个Rating类并汇总评级和评级平均值。

评级等级

这是一些伪代码,但意思应该是显而易见的。

{
  _id:ObjectId(…),
  rating: Integer,
  rater: User._id
  rated: User._id
  date: ISODate()
}

为了有效地进行聚合,您应该至少创建一个超过rated的索引:

db.ratings.ensureIndex({rated:1})

现在,您可以决定接近:或者,您计算评分的数量以及每小时一次的平均值,并将其存储在一个集合中,让我们说rate_averages ,或者按需计算这些值。

预先计算

db.ratings.aggregate(
  // Aggregation
  [{
     $order: {
      _id: "$rated",
      ratings: { $sum:1 },
      average: { $avg: "$rating" }
    },
    {$out:'rate_averages'}
  ]
)

rate_averages集合中的文档将如下所示:

{
  _id:User._id,
  ratings: Integer,
  average: Float
}

并且可以轻松查询各个用户的值,因为_id会自动编入索引。

按需

您使用相同的评分和几乎相同的聚合查询,但我们添加了$match阶段,因此我们只使用我们想要了解统计信息的用户的值并忽略$out阶段并将文档直接返回:

db.ratings.aggregate([
  {
    $match:{ rated: <_id of the user we want the values for> },
  },
  {
    $order: {
      _id: "$rated",
      ratings: { $sum:1 },
      average: { $avg: "$rating" }
  }
])

将返回单个文档,如相关用户所示。

通过这种方法和适当的数据模型,您甚至可以执行以下操作:&#34;特定用户在给定日期给出了多少评分?&#34;或者&#34;最活跃的评价者/评分最高的是什么?&#34;很容易。

请阅读aggregation framework docs了解更多详情。您可能会发现data modeling docs也很有用。

答案 2 :(得分:1)

以下代码可用于获取每个用户的平均评分。

db.ratings.aggregate([
 {
 $match:{ rated: '$user' },
 },
 {
 $order: {
  _id: "$rated",
  average: { $avg: "$rating" }
 }
 ])

答案 3 :(得分:1)

我的解决方案非常简单,类似于您的第三个选项,但更简单。假设我们有3个模型:BookUserRating。我添加了新的字段调用totalRated-到Book模型的int数组,用于存储总额定计数,该值正在映射index + 1

您的评分系统从1-5开始,因此totalRated的意思是:

  • [total1star, total2star, total3star, total4star, total5star]

每次用户对这本书进行评分时,我都会在Rating集合上创建一个Document,并将计数增加1(与index+1数组的totalRated映射)。

结果是:

  • rateCount现在是数组中每个项目的总和。
  • rateAverage应该是(index+1 * value) / rateCount
  • 我们也可以通过使用index + 1进行值映射来获得总号码费率。

一步一步

默认情况下,应为:

// Book Document
{
 _id,
 totalRated: [0, 0, 0, 0, 0],
 ...otherFields
}
  • 如果user1将此书评为5星,则文档现在应为:
{
 _id,
 totalRated: [0, 0, 0, 0, 1],
 ...otherFields
}
  • 如果user2将此书评为4星,则文档现在应为:
{
 _id,
 totalRated: [0, 0, 0, 1, 1],
 ...otherFields
}
  • 如果user3将此书评为4星,则文档现在应为:
{
 _id,
 totalRated: [0, 0, 0, 2, 1],
 ...otherFields
}
  • rateCount = 0 + 0 + 0 + 2 + 1 = 3
  • rateAverage = (0*1 + 0*2 + 0*3 + 2*4 + 1*5)/3 = 9.6666 ...

注意:您可以将数组int更改为数组对象,键应该是等级值,值应该是totalRating,但是数组int对我来说足够了。