Rails:优化从关联 table 中查询最大值
Rails: Optimize querying maximum values from associated table
我需要显示合作伙伴列表和 Klass
table 中 reservation_limit
列的最大值。
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
.
下的查询 运行s
SELECT MAX("klasses"."reservation_limit") FROM "klasses" WHERE "klasses"."partner_id" = [["partner_id", 1]]
如果有 40 个合作伙伴,则查询将 运行 40 次。我该如何优化它?
编辑:看起来 rails 中有一个 limit 方法,所以我将有问题的 limit
更改为 reservation_limit
以防止混淆。
这将 return 最大。 parthner_ids
:
数组的限制为 select
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}
您现在可以将其集成到您的视图中:
<% @partners.each do |partner| %>
Up to <%= @limits[partner.id].limit %> visits per month
<% end %>
您的初始查询提供了您需要的所有信息。您只需像使用常规对象数组一样使用它。
改变
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")
触发 Active Record 查询 SELECT MAX...
的作用。但是您不需要这个,因为您已经拥有处理数组中最大值所需的所有信息。
备注
在 Active Record 结果上使用 .count
将触发额外的 SELECT COUNT...
查询!
使用 .length
不会。
如果您开始使用纯 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
您可以使用两种形式的 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 要求您使用 "group by partner.*",但是,就所需的排序和溢出到磁盘的可能性而言,这可能很昂贵。
另一方面,您可以添加一个子句,例如:
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 中可能会在内部有效地转换为第一种形式,但由于在合作伙伴中每行执行一次子查询,因此执行效率可能较低 table(这仍然比原始 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 版本。
如果伙伴没有类时不需要结果,或者总是保证有一个,那么:
Partner.joins(:klasses).
select("partners.*, max(klasses.limit) as max_klasses_limit").
group(:id)
无论哪种方式,您都可以参考
partner.max_klasses_limit
我需要显示合作伙伴列表和 Klass
table 中 reservation_limit
列的最大值。
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" = [["partner_id", 1]]
如果有 40 个合作伙伴,则查询将 运行 40 次。我该如何优化它?
编辑:看起来 rails 中有一个 limit 方法,所以我将有问题的 limit
更改为 reservation_limit
以防止混淆。
这将 return 最大。 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}
您现在可以将其集成到您的视图中:
<% @partners.each do |partner| %>
Up to <%= @limits[partner.id].limit %> visits per month
<% end %>
您的初始查询提供了您需要的所有信息。您只需像使用常规对象数组一样使用它。
改变
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")
触发 Active Record 查询 SELECT MAX...
的作用。但是您不需要这个,因为您已经拥有处理数组中最大值所需的所有信息。
备注
在 Active Record 结果上使用 .count
将触发额外的 SELECT COUNT...
查询!
使用 .length
不会。
如果您开始使用纯 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
您可以使用两种形式的 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 要求您使用 "group by partner.*",但是,就所需的排序和溢出到磁盘的可能性而言,这可能很昂贵。
另一方面,您可以添加一个子句,例如:
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 中可能会在内部有效地转换为第一种形式,但由于在合作伙伴中每行执行一次子查询,因此执行效率可能较低 table(这仍然比原始 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 版本。
如果伙伴没有类时不需要结果,或者总是保证有一个,那么:
Partner.joins(:klasses).
select("partners.*, max(klasses.limit) as max_klasses_limit").
group(:id)
无论哪种方式,您都可以参考
partner.max_klasses_limit