Rails 3 - Turning multiple counts into a single query - OrderedHash
I've got an initializer method that does a silly thing. I need to optimize it down to one query, but my SQL skills are failing me at the moment. I've conceived of using GROUP BY and UNION and all sorts of things, but I just made myself more confused. I bequeath this to the community to shed some insight:
Class Stats
# Turn these three queries into one query that we can then
# load into the three different instance variables
def initialize(question)
# Integer = total number of answers for this question
@total = total_answers(question.id)
# Hash keyed by 0 (incorrect answers) and 1 (correct answers)
@stats_total = load_stats_total(question.id) if @total > 0
# Hash keyed by answer_id with values = total number of answers
@stats_answers = load_stats_answers(question.id) if @total > 0
end
# Returns an int = the total number of answer attempts for
# this question (right + wrong user_answers)
# Excludes anonymous users
def total_answers(question_id)
UserAnswer.count(:conditions => ['u.anonymous = 0 AND q.id = ?', question_id], :joins => 'JOIN answers a ON user_answers.answer_id = a.id JOIN questions q ON q.id = a.question_id JOIN users u ON u.id = user_answers.user_id')
end
# Returns an OrderedHash =
# {"0" => number of wrong user_answers for this question,
# "1" => number of correct user_answers for this question}
# Excludes anonymous users
def load_stats_total(question_id)
UserAnswer.count(:conditions => ['u.anonymous = 0 AND q.id = ?', question_id], :joins => 'JOIN answers a ON user_answers.answer_id = a.id JOIN questions q ON q.id = a.question_id JOIN users u ON u.id = user_answers.user_id', :group => 'a.correct')
end
# Returns an OrderedHash =
# {
# some_answer_id => total number of user_answers for this answer,
# some_other_answer_id => total number of user_answers for this answer
# ...
# }
# Excludes anonymous users
def load_stats_answers(question_id)
UserAnswer.count(:conditions => ['u.anonymous = 0 AND q.id = ?', question_id], :joins => 'JOIN answers a ON user_answers.answer_id = a.id JOIN questions q ON q.id = a.开发者_如何学Pythonquestion_id JOIN users u ON u.id = user_answers.user_id', :group => 'a.id')
end
end
If anyone has any thoughts, they'd be greatly appreciated! Thanks.
I don't think you can do this cleanly in one query. At least not without writing pure sql.
But lets try and find a nice solution in ActiveRecord
First of all, let's try to remove some of the sql
UserAnswer.count(:conditions => ['u.anonymous = 0 AND q.id = ?', question_id], :joins => 'JOIN answers a ON user_answers.answer_id = a.id JOIN questions q ON q.id = a.question_id JOIN users u ON u.id = user_answers.user_id')
can be rewritten
UserAnswer.joins(:user).where(:users => {:anonymous => false})\
.joins(:answer => :question).where(:questions => {:id => question_id})\
.count
lets just save this scope as a magic private method magic_scope
your current methods become
def total_answers(question_id)
magic_scope(question_id).count
end
def load_stats_total(question_id)
magic_scope(question_id).count(:group => "answers.correct")
end
def load_stats_answers(question_id)
magic_scope(question_id).count(:group => "answers.id")
end
notably, of course, the total_answers
method can be derived from summing up the results from either of the load_stats_*
methods.
If ActiveRecord was a bit more clever we could do
def all_the_magic(question_id)
magic_scope(question_id).count(:group => ["answers.correct", "answers.id"])
end
which would give us all the data we needed to do it in one query.
but as far as I'm aware that's not currently possible.
But I hope this gets you nearer.
精彩评论