Evening all,
I just had a quick question about SQL average queries.
I have a post model which has many ratings. All of the ratings for a particular post are added up and divided by the number of ratings for the average. So this calculation is declared in the post model and saved in the database. In my index view I list out all of the posts, with their author, name and their rating, which comes from the post rating db column.
My question is, why is rails performing all of these SQL average queries when these values have been saved in the database?
my code is as follows:
In the console
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
posts/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 index action (I’ve tried eager loading but the avg queries are still there)
def index
@paginate_posts = Post.paginate(page: params[:page], per_page: 10).includes(:user).search(params[:search])
end
SQL logs with lots of queries
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)
as usual if you need more code just shout.
many thanks, Andy
You have a method called rating here in post which overrides the accessor on your model, so even if you have a db column ‘rating’ it’s not going to be called when you ask for model.rating, the method will be, which will iterate ratings again. In addition the method is not saving (just setting an attribute does not persist to the db, you need to call save or similar). So if you want to save the value in rating, I’d get rid of that method.
You should really do this when the post has a rating added or is changed – at that point, call update_rating or something (see below), and then make sure you are calling post.save after (either make it a before_save callback, or call self.update_attribute or self.save explitly in update_rating) – that will save the change to the db, and then you can use
in views or anywhere else as a cached average rating value.
in Post model: