开发者

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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜