我们正在使用MongoDB,并且我正在计算用于存储评级的模式。
fromUser
这很好,但我的主要问题是设置它,以便重新计算平均值尽可能高效。
解决方案1 - 单独的评级等级
首先想到的是创建一个单独的Ratings
类,并在Ratings
类中存储一个指向User
的指针数组。我第二次猜到这一点的原因是,每次有新Ratings
进入时我们都必须查询所有Rating
个对象,以便我们可以重新计算平均值
...
解决方案2 - 用户类中的词典
第二个想法是直接在User
类中存储一个字典,用于存储这些Ratings
个对象。这比解决方案1稍微轻一点,但我们每次更新时都会重写每个用户的整个Ratings
历史记录。这似乎很危险。
...
解决方案3 - 用户类别中具有单独平均值的单独评级类
我们在自己的类中有Ratings
的混合选项,以及指向它们的指针数组,但是,我们在用户类中保留了两个值 - ratingsAve
和ratingsCount
。这样,当设置新的评级时,我们保存该对象,但我们可以轻松地重新计算ratingsAve
。
解决方案3听起来对我来说最好,但我只是想知道我们是否需要通过重新检查评级历史来重置ratingsAve
以确保所有内容都检出来包含定期校准。 / p>
我可能会过度思考这一点,但我在数据库模式创建方面并不是那么出色,这似乎是一个标准的模式问题,我应该知道如何实现。
哪个是确保一致性以及重新计算效率的最佳选择?
答案 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
会发生什么?
我强烈建议您在客户端根据rateCount
和rateValue
进行计算,使用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个模型:Book
,User
和Rating
。我添加了新的字段调用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
}
{
_id,
totalRated: [0, 0, 0, 0, 1],
...otherFields
}
{
_id,
totalRated: [0, 0, 0, 1, 1],
...otherFields
}
{
_id,
totalRated: [0, 0, 0, 2, 1],
...otherFields
}
0 + 0 + 0 + 2 + 1
= 3 (0*1 + 0*2 + 0*3 + 2*4 + 1*5)/3
= 9.6666 ... 注意:您可以将数组int更改为数组对象,键应该是等级值,值应该是totalRating,但是数组int对我来说足够了。