复杂的Rails查询

时间:2013-05-27 17:18:55

标签: ruby-on-rails postgresql activerecord ruby-on-rails-3.2

我的模型结构如下:

  1. 用户通过ResponseSets进行了多次调查(用户可以通过为每次尝试创建ResponseSets多次参加同一调查。)
  2. ResponseSet有很多回复
  3. 回复属于Questions,Answers和ResponseSets。
  4. 我需要一个rails查询来查找:

    1. 有多少用户在与答案相对应的给定调查的最后一次ResponseSet(意味着最后一次尝试)中选择了给定答案。

    2. 有多少用户在针对与问题对应的给定调查的最后一个ResponseSet(意味着最后一次尝试)中选择了一个给定问题。

2 个答案:

答案 0 :(得分:2)

由于您使用的是PostgreSQL,我建议在子查询中使用窗口函数来简化逻辑。子查询将收集给定查询的所有response_sets。外部查询过滤到该调查的每个用户的最后一个ResponseSet(基于窗口函数生成的rowNum),并确保它包含与给定答案关联的响应:

select_sql <<-SELECT
  response_sets.*,
  ROW_NUMBER() OVER (PARTITION BY response_sets.user_id ORDER BY response_sets.id DESC) as rowNum
SELECT

subquery = survey.response_sets.select(select_sql).to_sql

ResponseSet.joins(:responses).from(Arel.sql("(#{subquery}) response_sets")).
            where(responses: {answer_id: answer.id}).
            where("rowNum = 1").count

请注意,这假设给定的答案每个ResponseSet只能使用一次。如果不是这种情况,您可以将.count替换为.count(:distinct => :user_id)

进一步优化

答案 1 :(得分:1)

正如我在评论中提到的,你需要一个子查询或Postgresql WITH子句来查找最新的响应,如果有很多ReponseSet,即使使用索引也会非常昂贵。

另一方面,如果您的表只包含最新的响应,那么将提供简单的嵌套连接。如果我正确理解您的架构,那么这些都很接近:

User.count(:joins => { :survey => {:latest_response_set => { :response => :answer }}}, 
           :conditions => ['answers.id = ?', answer_id])

User.count(:joins => { :survey => {:latest_response_set => { :response => :question}}}, 
           :conditions => ['questions.id = ? and surveys.id = ?', question_id, survey_id])

您可以使用after_save回调更新最新的响应表。这是相对安全的,因为回调包含在事务中。