Rails:优化查询关联表中的最大值

时间:2015-09-05 09:50:04

标签: sql ruby-on-rails ruby postgresql rails-postgresql

我需要显示合作伙伴列表以及reservation_limit表中Klass列的最大值。

Partner  has_many    :klasses
Klass    belongs_to  :partner

# Partner controller
def index
  @partners = Partner.includes(:klasses)
end

# view
<% @partners.each do |partner| %>
  Up to <%= partner.klasses.maximum("reservation_limit") %> visits per month
<% end %>

不幸的是,下面的查询针对每个Partner运行。

SELECT MAX("klasses"."reservation_limit") FROM "klasses" WHERE "klasses"."partner_id" = $1  [["partner_id", 1]]

如果有40个合作伙伴,则查询将运行40次。我该如何优化它?

编辑:看起来在导轨中有limit方法,因此我将有问题的limit更改为reservation_limit以防止混淆。

4 个答案:

答案 0 :(得分:1)

您的初始查询会带来您需要的所有信息。您只需使用它就像使用常规对象数组一样。

更改

Up to <%= partner.klasses.maximum("reservation_limit") %> visits per month

Up to <%= partner.klasses.empty? ? 0 : partner.klasses.max_by { |k| k.reservation_limit }.reservation_limit %> visits per month

maximum("reservation_limit")会触发有效记录查询SELECT MAX...。但是你不需要这个,因为你已经掌握了处理数组中最大值所需的所有信息。

注意
在Active Record结果上使用.count将触发额外的SELECT COUNT...查询! 使用.length不会。

答案 1 :(得分:1)

如果您开始在纯SQL中编写查询,然后将其提取到ActiveRecord或Arel代码中,通常会有所帮助。

ActiveRecord非常强大,但是一旦您脱离标准的CRUD操作,它就会强迫您编写效率极低的查询。

这是您的查询

Partner
    .select('partners.*, (SELECT MAX(klasses.reservation_limit) FROM klasses WHERE klasses.partner_id = partners.id) AS maximum_limit')
    .joins(:klasses).group('partners.id')

这是一个带有子查询的查询。但是,子查询被优化为仅运行一次,因为它可以在前面解析并且不会运行N + 1次。

上面的代码获取所有合作伙伴,并使用klasses记录加入它们,并且由于联接,它可以计算聚合最大值。由于联接有效地创建了记录的笛卡尔积,因此您需要按partners.id进行分组(事实上,MAX聚合函数实际上是必需的。)

此处的关键是AS maximum_limit,它会为使用计数值返回的Partner个实例分配一个新属性。

partners = Partner.select ...
partners.each do |partner|
  puts partner.maximum_limit
end

答案 2 :(得分:1)

你可以使用两种形式的SQL来有效地检索这些信息,我假设你想要一个合作伙伴的结果,即使它没有klass记录

第一个是:

   select partners.*,
          max(klasses.limit) as max_klasses_limit
     from partners
left join klasses on klasses.partner_id = partners.id
 group by partner.id

有些RDBMS要求您使用&#34;按伙伴分组。*&#34;但是,就所需的排序和溢出到磁盘的可能性而言,这可能很昂贵。

另一方面,您可以添加一个子句,如:

having("max(klasses.limit) > ?", 3)

...通过最大值klass.limit

有效地过滤合作伙伴

另一个是:

   select partners.*,
          (Select max(klasses.limit)
             from klasses
            where klasses.partner_id = partners.id) as max_klasses_limit
     from partners

第二个不依赖于group by,并且在某些RDBMS中可以在内部有效地转换为第一个形式,但是可以通过在partner表中每行执行一次子查询来执行效率较低(这将是stil比每行实际提交查询的原始Rails方法快得多。

这些Rails ActiveRecord形式将是:

Partner.joins("left join klasses on klasses.partner_id = partners.id").
        select("partners.*, max(klasses.limit) as max_klasses_limit").
        group(:id)

......和......

Partner.select("partners.*, (select max(klasses.limit)
               from klasses
               where klasses.partner_id = partners.id) as max_klasses_limit")

其中哪一项实际上效率最高可能取决于RDBMS甚至RDBMS版本。

如果您在合作伙伴没有klass时不需要结果,或者总是保证合格,那么:

Partner.joins(:klasses).
        select("partners.*, max(klasses.limit) as max_klasses_limit").
        group(:id)

无论哪种方式,您都可以参考

partner.max_klasses_limit

答案 3 :(得分:0)

这将返回最大值。限制在def picture_under_size_limit数组的一个选择中:

parthner_ids

您现在可以将其集成到您的视图中:

parthner_ids = @partners.map{|p| p.id}
data = Klass.select('MAX("limit") as limit', 'partner_id').where(partner_id: parthner_ids).group('partner_id')
@limits = data.to_a.group_by{|d| d.id}