I need some help optimizing the following method. The queries have become too costly as my database has grown in size to 3K users and 100K workouts. Unfortunately my SQL skills are rudimentary.
The method returns the rank of a given workout against other workouts by people of the same gender, and the total number of workouts for that gender. There are other applicable conditions to the workouts which I have removed to simplify this sample:
Class Person
has_many :workouts
named_scope :men, :conditions => {:male => true}
end
Class Workout
belongs_to :person
named_scope :by, lambda {|person_id| {:conditions => {:person_id => person_id}}}
def rank_by_gender
people = Person.men.collect{|p| p.id}
{ :place =>
Workout.by(people).count(:conditions => ["time < ?", self.time]) + 1
:entries =>
Workout.by(people).count() }
end
end
Person.men now returns several thousand records. Is there a way to combine that query with the count query, possibly by using a join on the people table with on the :male attribute?
Is there a way to put a condition on the Person association into the conditions passed to the count method?
Beyond that, does anyone see other ways to reduce the size and time of database queries in this method?
This should work for you:
{
:place => Workout.joins(:person).where(["`workouts`.time < ? AND `people`.male = ?", self.time, true]).size,
:entries => Workout.joins(:person).where(:people => {:male => true}).size
}
Related
We build some object in our controller:
#sites = Site.find(:all, :conditions => conditions, :order => 'group_id ASC')
And then in the view (currently), we are doing something like:
#sites.each do |site|
%p Site Name
= site.name
- actual = site.estimates.where('date<?', Date.today).sum(:actual_cost)
%p
= actual
end
Basically like that.
And of course this fires off a query for the Sites and then a query for N sites returned. I know about eager-loading associations with #sites = Site.find(:all, :include => :estimates) but in this case it didn't matter because we're doing the SUM on the query which is special it seems.
How would I eager load the SUMs in such that I don't get all the crazy queries? Currently it's over 600...
provide your conditions & ordering in this query only, which will push the result into a Hash.
sites = Site.includes(:estimates).where('estimates.date < ? ', Date.today)
.order('sites.id ASC')
actual = 0
sites.map { |site|
site.estimates.map { |estimate| actual = actual + estimate.actual_cost }
}
From your explanation, I am assuming actual_cost is a column in estimate table.
For example, I have:
class School < ActiveRecord::Base
has_many :students
end
one_school = School.first
Is there a speed difference between using:
Student.find :all, :conditions => { :first_name => "John", :school_id => one_school.id }
and
one_school.students.find :all, :conditions => { :first_name => "John" }
I was wondering if calling "one_school.students.find" will iterate through ALL the student records, or will they iterate through the student records belonging to one_school only?
This is more of a question on performance. I need to know if the latter query really is faster in rails.
That's basically the same (Rails will use a join in both cases), you can see the query and it's performance in your server console and compare. To enhance performance make sure you index school_id on your students table.
I have a typical forum style app. There is a Topics model which has_many Posts.
What I want to do using Rails 2.3.x is query the topics table and sort by the most recent post in that topic.
#topics = Topic.paginate :page => params[:page], :per_page => 25,
:include => :posts, :order => 'HELP'
I'm sure this is a simple one but no joy with Google. Thanks.
Sorting on a joined column is probably a bad idea and will take an enormous amount of time to run in many situations. What would be better is to twiddle a special date field on the Topic model when a new Post is created:
class Post < ActiveRecord::Base
after_create :update_topic_activity_at
protected
def update_topic_activity_at
Topic.update_all({ :activity_at => Time.now }, { :id => self.topic_id})
end
end
Then you can easily sort on the activity_at column as required.
When adding this column you can always populate the initial activity_at with the highest posting time if you have existing data to migrate.
I'm trying to implement Ryan Bates' sortable table columns code (Railscast #228) but I'd like to be able to sort on an associated column. In particular, I have the following models and associations:
class Project < ActiveRecord::Base
belongs_to :program_manager, :class_name => "User"
class User < ActiveRecord::Base
has_many :program_manager_projects, :class_name => "Project", :foreign_key => "program_manager_id"
The association between the Project model and the User model is mediated by the 'program_manager_id' foreign key, which the user sets in the new/edit views using a collection-select dropdown. Here's part of the annotation at the top of project.rb:
# Table name: projects
# program_manager_id :integer
I want to be able to sort my list of projects in the index view by the program manager's name, i.e., by project.program_manager.name.
Ideally, I'd be able to point :order to this name somehow, perhaps with something like this in the index method of my ProjectsController:
#projects = Project.find(:all, :order => project.program_manager.name)
But that obviously won't work (not to mention Ryan's routine implements this with a specific reference to table names from the model to be sorted.)
I've come across some intimidating approaches that use named_scope, such as:
named_scope :most_active, :select => "questions.*", :joins => "left join comments as comments_for_count on comments_for_count.question.id = questions.id", :group => "questions.id", :order => "count(questions.id) desc"
But given my lack of MySQL expertise, this is fairly impenetrable to me.
Can anyone help me either generalize the named_scope example above for my specific case, or point me to a more straightforward strategy?
Thanks very much,
Dean
Let's dissect that named scope you referenced above. Imagine a model Question which has many Comments.
named_scope :most_active, :select => "questions.*", :joins => "left join comments as comments_for_count on comments_for_count.question.id = questions.id", :group => "questions.id", :order => "count(questions.id) desc"
:most_active
the name of your scope. You would reference thusly: Question.find(:all).most_active
:select => "questions.*"
by default scopes selects all columns from your table anyway, so this limits the results to only the questions table, and not the comments table. This is optional.
:joins => "left join comments as comments_for_count on comments_for_count.question.id = questions.id"
this is saying for every question, I also want to get all comments associated with them. The comments table has a column 'question_id' which is what we'll be using to match them up to the appropriate question record. This is important. It allows us access to fields that are not on our model!
:group => "questions.id"
This is required for the count() function in the order clause to tell us that we want the count of comments based on question. We don't need the count function in our order clause, so we also don't need this group statement
:order => "count(questions.id) desc"
Return the results in order of number of comments, highest to lowest.
So for our example, discarding what we don't need, and applying to your needs, we end up with:
:named_scope :by_program_manager_name, :joins => "left join users on projects.program_manager_id = users.id", :order => "users.name"
This named_scope would be called thusly:
Project.find(:all).by_program_manager_name
Note this is basically equivalent to:
Project.find(:all, :joins => "left join users on projects.program_manager_id = users.id", :order => "users.name")
But, as cam referenced above, you should really know the underlying SQL. Your abilities will be severely hampered without this understanding
I'm working on Ruby On Rails 2.3.2 and I'm learning how to use the :include statement.
I'd like to get all the announcements order by their rate. I've got two models and tables for that: Announcement and Rate.
The models look like this:
class Announcement <
ActiveRecord::Base
belongs_to :rate
end
class Rate < ActiveRecord::Base
belongs_to :announcement
end
I'd like to do something like the following, but using :include statement.
Announcement.find_by_sql 'select * from announcements ann inner join rates r on ann.id = r.announcement_id order by r.average DESC'
I already tried this:
Announcement.paginate :page => params[:page], :per_page => 10, :include => [:rates], :order => 'rates.average DESC'
but it's trying to associate rates.id = announcements.id, instead of rates.announcement_id = announcement_id
How can I specify the correct relationship to make this work?
As Yuri points out in the comments, you have incorrectly defined your relationship.
Announcements should have_one :rate
As a named scope on announcment:
class Announcement < ActiveRecord::Base
has_one :rate
named_scope :order_by_rate, :include => :rate, :order => 'rates.average DESC'
end
Announcement.order_by_rate.paginate :per_page => 10, :page => params[:page]
Note the difference between :include and :joins as a find option. :joins will join the on the association(s) or SQL JOIN fragment given for purposes of complex WHERE/ORDER clauses. :include will only works on associations, it provides the same advantages of :joins, and also eager load the association.
Where eager loading makes a difference:
With include, #a.rate is populated in the same SQL statement that populates #a.
#a = Association.first :include => :rate
Without include #a.rate is not populated until required.
#a = Association.first
#a.rate #=> #a.rate is populated here, with a separate SQL statement.