晚上好,
我刚才有一个关于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索引操作(我尝试过急切加载,但avg查询仍在那里)
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)
像往常一样如果你需要更多的代码只是喊叫。
非常感谢,安迪
答案 0 :(得分:2)
你在post中有一个名为rating的方法,它会覆盖你模型上的访问器,所以即使你有一个db列'rating',当你要求model.rating时它也不会被调用,方法将是,这将再次迭代收视率。另外该方法没有保存(只是设置属性不会持久存储到db,需要调用save或类似)。因此,如果您想将值保存在评级中,我将摆脱该方法。
当帖子添加或更改评级时,你应该真的这样做 - 此时,调用update_rating或其他东西(见下文),然后确保你之后调用post.save(或者让它成为before_save回调,或者在update_rating中explitly调用self.update_attribute或self.save) - 这会将更改保存到db,然后你可以使用
<%= post.rating %>
在视图或其他任何位置作为缓存平均评级值。
帖子模型中的:
before_save :update_rating
def update_rating
if self.ratings.any?
self.rating = self.ratings.average(:rating)
end
end
答案 1 :(得分:1)
你的帖子循环。在每个循环中,您调用:
post.rating
反过来触发你的AVG SQL查询:
self.ratings.average(:rating)
它没有保存,它只是计算所以你的'if post.rating ='工作
答案 2 :(得分: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
是对DB的调用,而不是在此阶段的内存中。 update_columns
允许您重写记录,而不会导致额外的save
操作,从而触发所有回调。在编写列时使用update_rating
方法也会设置内存中的评级属性,除非您之后还重新加载模型,否则update_columns
不会执行此操作。