Rails 3 - 将多个计数转换为单个查询 - OrderedHash

时间:2011-06-13 20:51:13

标签: mysql ruby-on-rails optimization activerecord count

我有一个初始化方法,做一个愚蠢的事情。我需要优化它到一个查询,但我的SQL技能目前让我失望。我已经想到了使用GROUP BY和UNION以及各种各样的东西,但我让自己更加困惑。我把这遗赠给社区,以便提供一些见解:

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.question_id JOIN users u ON u.id = user_answers.user_id', :group => 'a.id')
  end
end

如果有人有任何想法,他们将不胜感激! 感谢。

1 个答案:

答案 0 :(得分:3)

我认为你不能在一个查询中干净利落地做到这一点。 至少没有编写纯sql。

但是我们试着在ActiveRecord中找到一个很好的解决方案

首先,让我们尝试删除一些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')

可以改写

UserAnswer.joins(:user).where(:users => {:anonymous => false})\
  .joins(:answer => :question).where(:questions => {:id => question_id})\
  .count

我们可以将此范围保存为魔术私有方法magic_scope

您当前的方法变为

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

值得注意的是,total_answers方法当然可以通过总结load_stats_*方法中的任何一种方法得出。

如果ActiveRecord更聪明我们可以做到

def all_the_magic(question_id)
  magic_scope(question_id).count(:group => ["answers.correct", "answers.id"])
end

这将为我们提供在一次查询中完成所需的所有数据。

但据我所知,目前还不可能。

但我希望这能让你更接近。