创建连接到子查询的Rails 3范围

时间:2013-04-20 18:58:22

标签: sql ruby-on-rails activerecord

首先,我是Ruby / Rails的新手,所以如果这个问题是基本的,我会道歉。

我有一个DB(其中包括)看起来像这样:

organizations { id, name, current_survey_id }
surveys { id, organization_id }
responses { id, survey_id, question_response_integer }

我正在尝试创建一个范围方法,将当前调查答案的平均值添加到传入的组织关系中。换句话说,传递给方法的范围会生成看起来像这样或多或少的SQL:

select * from organizations

我希望范围在我的lambda处理之后生成如下所示的SQL:

select o.id, o.name, cs.average_responses
from organizations o join
(select r.id, avg(r.question_response_integer) as average_responses 
     from responses r 
     group by r.id) cs on cs.id = o.current_survey_id

我得到的最好的是这样的:

current_survey_average: lambda do |scope, sort_direction|
  average_answers = Responses.
     select("survey_id, avg(question_response_integer) as average_responses").
     group("survey_id")
  scope.joins(average_answers).order("average_responses #{sort_direction}")
end

这主要只是在黑暗中刺伤 - 除此之外,它没有具体说明范围如何加入average_answers - 但我无法找到任何关于如何加入的文档做那种加入,我的事情已经不多了。

有什么建议吗?

编辑:感谢Sean Hill的回答。只是记录在案,这是我最终的代码:

current_survey_average: lambda do |scope, sort_direction|
  scope_table = scope.arel.froms.first.name
  query = <<-QUERY
    inner join (
     select r.survey_id, avg(r.question_response_integer) as average_responses
      from responses r
      group by r.survey_id
    ) cs
    on cs.survey_id = #{scope_table}.current_survey_id
  QUERY
  scope.
    joins(query).
    order("cs.average_responses #{sort_direction}")
end

那就是说,我可以看到将averaged_answers范围直接放在Responses类上的好处 - 所以我最终可能会这样做。

1 个答案:

答案 0 :(得分:1)

我无法对此进行测试,但我认为以下内容可以正常使用,也可以进行一些调整。

class Response < ActiveRecord::Base
  scope :averaged, -> { select('r.id, avg(r.question_response_integer) as average_responses').group('r.id') }

  scope :current_survey_average, ->(incoming_scope, sort_direction) do
    scope_table = incoming_scope.arel.froms.first.name
    query = <<-QUERY
      INNER JOIN ( #{Arel.sql(averaged.to_sql)} ) cs
      ON cs.id = #{scope_table}.current_survey_id
    QUERY

    incoming_scope.joins(query).order("average_responses #{sort_direction}")
  end
end

所以我在这里所做的就是我将内部查询拆分为另一个名为averaged的范围。由于您不知道current_survey_average中的传入范围来自哪个表,因此我通过scope.arel.froms.first.name获得了范围表名称。然后我创建了一个使用averaged范围的查询字符串,并使用scope_table变量将其连接起来。其余的都是不言自明的。

如果您确实知道传入范围将始终来自组织表,那么您不需要额外的scope_table变量。您只需将其硬编码到连接查询字符串中即可。

我会提出一个建议。如果您无法控制sort_direction,那么我不会直接将其输入到订单字符串中。