最佳实践:我应该在我的数据库中存储哪些信息?

时间:2016-02-05 09:13:21

标签: mysql ruby-on-rails

目前我正在开发一个小型图书评级应用,用户可以对图书进行评分和评论。

当然我有一本书模型:

class Book < ActiveRecord::Base
  has_many :ratings
end

和评级模型:

class Rating < ActiveRecord::Base
  belongs_to :book
end

评级对象的“总评级值”由不同的评级类别(例如,可读性,......)计算。此外,一本书的总评分应按所有给定的评级计算。

现在我问自己的问题:我是否应该计算/查询每个人访问我页面的每一本书的整体评分,还是应该在我的图书模型中添加一个字段,其中(定期)计算并保存整体评分? / p>

编辑:我在这种情况下使用的“计算”是一个简单的平均判断。

示例:一本书有大约200个评级。每个评级都是10个类别评级的组合。所以我想确定一个评级的平均值,并在所有200个评级中结束。

5 个答案:

答案 0 :(得分:2)

如果这些评级的平均值在计算上并不昂贵(即不需要很长时间),那么只需动态计算即可。这符合不过早使用的想法(见http://c2.com/cgi/wiki?PrematureOptimization)。

但是,如果您确实希望优化此计算,然后将其存储在书籍模型上并更新评级写入的计算,那么就可以了。这被称为“缓存”结果。以下是一些缓存数据库中平均评级的代码。 (还有其他缓存方式)。

class Book < ActiveRecord::Base
  has_many :ratings, after_add :update_average_rating

  def update_average_rating
    update_attribute(:average_rating, average_rating)
  end

  def average_rating
    rating_sum / ratings.count
  end

  def rating_sum
    ratings.reduce(0) {|sum, rating|
      sum + rating.value # assuming rating model has a value attribute
    }
  end
end

class Rating < ActiveRecord::Base
  belongs_to :book
end

注意:上面的代码假定数据库中的book表中存在average_rating列。请务必使用迁移添加此列。

答案 1 :(得分:2)

<强> DB

最有效(尽管不是传统)方式是使用数据库级ALIAS列,允许您计算每个{{1}的评级AVGSUM呼叫:

book

这将允许:

#app/models/book.rb
class Book < ActiveRecord::Base
   def reviews_avg category
      cat = category ? "AND `category` = \"#{category}\"" : ""
      sql = "SELECT AVG(`rating`) FROM `reviews` WHERE `book_id` = #{self.id} #{cat})
      results = ActiveRecord::Base.connection.execute(sql)
      results.first.first.to_f
   end
end

这是最有效的,因为它完全由DB处理:

enter image description here

<强>滑轨

应该使用Rails的average功能:

@book = Book.find x
@book.reviews_avg               # -> 3.5
@book.reviews_avg "readability" # -> 5

通过以上操作,您可以调用#app/models/book.rb class Book < ActiveRecord::Base has_many :ratings do def average category if category where(category: category).average(:rating) else average(:rating) end end end end 实例,并评估@bookaverage的评分:

total

-

您还可以在@book = Book.find x @book.reviews.average #-> 3.5 @book.reviews.average "readability" #-> 5 上使用class method / scope

Review

这将允许您致电:

#app/models.review.rb
class Review < ActiveRecord::Base
   scope :avg, (category) -> { where(category: category).average(:rating) }
end

关联扩展程序

另一种方式(未经测试)是在ActiveRecord Association Extension中使用@book = Book.find x @book.reviews.avg #-> 3.5 @book.reviews.avg "readability" #-> 5 对象。

虽然效率不如数据库级别的查询,但它可以让您在内存中执行活动:

proxy_association.target

这将允许您致电:

#app/models/book.rb
class Book < ActiveRecord::Base
   has_many :reviews do
     def avg category
       associative_array = proxy_association.target
       associative_array = associative_array.select{|key, hash| hash["category"] == category } if category
       ratings = associative_array.map { |a| a["rating"] }
       ratings.inject(:+) / associative_array.size #-> 35/5 = 7
     end
   end
end

答案 2 :(得分:1)

根本不需要重新计算每次访问页面的平均总体评分,因为它只会在有人对该书进行评分时发生变化。所以只需使用字段AVG_RATING或类似的东西,并更新每个给定评级的值。

答案 3 :(得分:1)

您是否考虑过使用评级的缓存版本?

rating = Rails.cache.fetch("book_#{id}_rating", expires_in: 5.minutes) do
  do the actual rating calculation here
end

答案 4 :(得分:1)

在大多数情况下,您只需查询数据库即可获得平均值:

average = book.reviews.average(:rating)

在大多数情况下,每次请求查询都不会太昂贵,这将成为一个真正的问题 - 而且,过早优化可能会浪费时间和资源,正如Neil Atkinson指出的那样。

然而,当计算成本成为问题时,有几种方法需要考虑,这取决于计算数据的性质。

如果计算出的数据是值得的资源,则可以将其保存在数据库中。例如,定期生成的报告(每日,每月,每年)以及需要查询的报告。

否则,如果计算出的数据具有较高的“流失率”(每天都会创建很多评论),您可以使用缓存来尽可能避免昂贵的查询,但将数据填充到数据库中可能会导致过多的缓慢UPDATE查询并绑定您的网络或工作流程。

有许多缓存方法相互补充:

  • etags利用客户端缓存 - 如果响应没有改变,请不要重新渲染。
  • 片段缓存可避免数据库查询,并为未更改的数据重新呈现视图块。
  • Memcached或Redis中的模型缓存可用于避免慢速查询。
  • 低级别缓存可用于存储平均值等内容。

有关详细信息,请参阅Caching with Rails: An overview