0

晚上所有,

我刚刚有一个关于 SQL 平均查询的快速问题。

我有一个有很多评级的帖子模型。将特定帖子的所有评分相加并除以平均评分数。所以这个计算在 post 模型中声明并保存在数据库中。在我的索引视图中,我列出了所有帖子及其作者、姓名和评级,这些来自帖子评级数据库列。

我的问题是,当这些值已保存在数据库中时,为什么 rails 会执行所有这些 SQL 平均查询?

我的代码如下:

在控制台中

Post Load (1.0ms)  SELECT `posts`.* FROM `posts` ORDER BY posts.created_at ASC LIMIT 1
 => #<Post id: 48, user_id: 5, song_name: "Enter Sandman", song: "ewwe wer ere rr erew rewr r erw erw rewedwde", created_at: "2012-08-16 13:35:32", updated_at: "2012-08-22 21:11:34", rating: 5.0, ratings_count: 2> 

Post.rb

def rating
    if self.ratings.any?
      self.rating = self.ratings.average(:rating)
    end
  end

帖子/index.html.erb

<legend>Music library</legend>  

    <%= will_paginate @paginate_posts %>

            <% if @paginate_posts.any? %>
                <% @paginate_posts.each do |post| %>
            <h4><%= link_to post.song_name, post %></h4>
                Author: <%= link_to post.user.name, "#" %><br/>
                    <% if post.rating == nil %>
                        No one has rated this yet<br/>
                    <% else %>
                        Rating: <%= post.rating %>/10<br/>
                    <% end %>
                <br/>
        <% end %>
<% end %>

<%= will_paginate @paginate_posts %>

posts_controller.rb 索引操作(我尝试过急切加载,但平均查询仍然存在)

def index
    @paginate_posts = Post.paginate(page: params[:page], per_page: 10).includes(:user).search(params[:search])
  end

包含大量查询的 SQL 日志

Started GET "/" for 127.0.0.1 at 2012-08-22 22:51:38 +0100
Processing by PostsController#index as HTML
  Post Load (0.3ms)  SELECT `posts`.* FROM `posts` ORDER BY posts.created_at DESC LIMIT 10 OFFSET 0
  User Load (0.3ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` IN (5)
   (0.3ms)  SELECT COUNT(*) FROM `posts` 
  Rendered posts/_copy.html.erb (0.1ms)
   (0.3ms)  SELECT AVG(`ratings`.`rating`) AS avg_id FROM `ratings` WHERE `ratings`.`post_id` = 63
  CACHE (0.0ms)  SELECT AVG(`ratings`.`rating`) AS avg_id FROM `ratings` WHERE `ratings`.`post_id` = 63
   (0.3ms)  SELECT AVG(`ratings`.`rating`) AS avg_id FROM `ratings` WHERE `ratings`.`post_id` = 62
  CACHE (0.0ms)  SELECT AVG(`ratings`.`rating`) AS avg_id FROM `ratings` WHERE `ratings`.`post_id` = 62
   (0.2ms)  SELECT AVG(`ratings`.`rating`) AS avg_id FROM `ratings` WHERE `ratings`.`post_id` = 61
  CACHE (0.0ms)  SELECT AVG(`ratings`.`rating`) AS avg_id FROM `ratings` WHERE `ratings`.`post_id` = 61
   (0.2ms)  SELECT AVG(`ratings`.`rating`) AS avg_id FROM `ratings` WHERE `ratings`.`post_id` = 60
  CACHE (0.0ms)  SELECT AVG(`ratings`.`rating`) AS avg_id FROM `ratings` WHERE `ratings`.`post_id` = 60
   (0.2ms)  SELECT AVG(`ratings`.`rating`) AS avg_id FROM `ratings` WHERE `ratings`.`post_id` = 59
  CACHE (0.0ms)  SELECT AVG(`ratings`.`rating`) AS avg_id FROM `ratings` WHERE `ratings`.`post_id` = 59
   (0.2ms)  SELECT AVG(`ratings`.`rating`) AS avg_id FROM `ratings` WHERE `ratings`.`post_id` = 58
  CACHE (0.0ms)  SELECT AVG(`ratings`.`rating`) AS avg_id FROM `ratings` WHERE `ratings`.`post_id` = 58
   (0.2ms)  SELECT AVG(`ratings`.`rating`) AS avg_id FROM `ratings` WHERE `ratings`.`post_id` = 57
  CACHE (0.0ms)  SELECT AVG(`ratings`.`rating`) AS avg_id FROM `ratings` WHERE `ratings`.`post_id` = 57
   (0.2ms)  SELECT AVG(`ratings`.`rating`) AS avg_id FROM `ratings` WHERE `ratings`.`post_id` = 56
  CACHE (0.0ms)  SELECT AVG(`ratings`.`rating`) AS avg_id FROM `ratings` WHERE `ratings`.`post_id` = 56
   (0.2ms)  SELECT AVG(`ratings`.`rating`) AS avg_id FROM `ratings` WHERE `ratings`.`post_id` = 55
  CACHE (0.0ms)  SELECT AVG(`ratings`.`rating`) AS avg_id FROM `ratings` WHERE `ratings`.`post_id` = 55
   (0.3ms)  SELECT AVG(`ratings`.`rating`) AS avg_id FROM `ratings` WHERE `ratings`.`post_id` = 54
  CACHE (0.0ms)  SELECT AVG(`ratings`.`rating`) AS avg_id FROM `ratings` WHERE `ratings`.`post_id` = 54
  Rendered posts/index.html.erb within layouts/application (60.8ms)
  User Load (0.4ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` = 5 LIMIT 1
  Rendered layouts/_footer.html.erb (0.1ms)
Completed 200 OK in 2012ms (Views: 1871.7ms | ActiveRecord: 33.1ms)

像往常一样,如果您需要更多代码,请大喊。非常感谢,安迪

4

3 回答 3

2

您在帖子中有一个名为 rating 的方法,它会覆盖模型上的访问器,因此即使您有一个 db 列“rating”,当您请求 model.rating 时它也不会被调用,该方法将是,它将迭代再次收视。另外该方法不保存(只是设置一个属性不会持久化到数据库,您需要调用保存或类似的方法)。因此,如果您想保存评级中的值,我会摆脱这种方法。

当帖子添加或更改评级时,您应该这样做 - 此时,调用 update_rating 或其他东西(见下文),然后确保您在调用 post.save 之后(要么将其设为 before_save 回调,要么调用self.update_attribute 或 self.save 明确地在 update_rating 中) - 这会将更改保存到数据库,然后您可以使用

<%= post.rating %>

在视图或其他任何地方作为缓存的平均评分值。

在 Post 模型中:

before_save :update_rating

def update_rating
  if self.ratings.any?
    self.rating = self.ratings.average(:rating)
  end
end
于 2012-08-23T09:38:33.877 回答
1

你循环你的帖子。在您调用的每个循环中:

post.rating 

这反过来会触发您的 AVG SQL 查询:

self.ratings.average(:rating)

它没有保存,它只是计算所以你的'if post.rating ='有效

于 2012-08-23T08:57:48.647 回答
1

根据我的经验,我发现@Kenny-Grant 的update_rating方法在计算和保持更新的评分时实际上不会在平均值中包含新添加的评分。

这对我有用:

after_save :update_rating!

def update_rating
  self.rating = ratings.average(:rating) || 0.0
end

def update_rating!
  self.update_columns(rating: update_score)
end

这确保新添加的记录显示出来,因为ratings这是对数据库的调用,而不是在这个阶段的内存中。让您重写记录而不会导致触发其所有回调update_columns的额外操作。在写入列时save使用该方法还会设置内存评级属性,除非您之后还重新加载模型,否则不会这样做。update_ratingupdate_columns

于 2014-01-10T14:26:34.080 回答