在我的Rails应用程序中,我具有以下关联:
class Person < ApplicationRecord
has_many :payments
def sales_volume(range)
payments.in_range(range).sum(&:amount)
end
...
end
class Payment < ApplicationRecord
belongs_to :person
def self.in_range(range)
select { |x| range.cover?(x.date) }
end
...
end
在我的控制器中,我正在做...
@people = Person.all.sort_by { |p| p.sales_volume(@range) }
...而且有效。
但是,不幸的是,它会生成很多 N + 1个查询。
有没有一种方法可以显示每个person
的销量,而无需为每个人生成单独的SQL查询?
添加:
我已经尝试过快速加载...
@people = Person.all.includes(:payments).sort_by { |p| p.sales_volume(@range) }
...但是那也不起作用。查询数量仍然相同。
答案 0 :(得分:5)
首先,让我们在in_range
中重写该ActiveRecord
方法
def self.in_range(range)
where(date: range)
end
现在,那个sales_volume
。假设
def self.with_sales_volume(range)
joins("LEFT JOIN payments ON payments.user_id = users.id").
merge(Payment.in_range(range)).
group('users.id').
select('users.*, SUM(payments.amount) AS sales_volume')
end
最后
@people = Person.from(Person.with_sales_volume(@range), :t).order('t.sales_volume DESC')
这是主意。我已经做过很多次了,但是如果不尝试并获取所有信息,这真的很难。我认为您可以解决此问题。如果您使用的是Rails 5,则可以使用left_joins
答案 1 :(得分:4)
ursus的答案应该是正确的,但我想提供有关您编写的代码的其他信息。
您在select
方法中执行的self.range
是一个Enumerable或Array方法,该方法在查询运行后对其进行操作,这是您看到如此多查询的原因之一。 Enumerable#select
ActiveRecord中的select
实际上选择了要检索的列:
Person.select(:id, :name) # only returns the columns id & name
您要执行where
:
class Payment < ApplicationRecord
belongs_to :person
def self.in_range(range)
where('date between ? and ?', range.begin, range.end)
end
end
这使您可以执行以下操作:
Person.first.sales_volume(Time.zone.today - 30.days..Time.zone.today)
这仅适用于单个人(这可能仍然有用)。
如果您想对集合执行这种类型的查询,您将需要一些更复杂的操作,如ursus指出。
people =
Person.
select('people.*, SUM(payments.amount) AS sales_volume').
joins(:payments).
merge(
Payment.
in_range(
(Time.zone.today - 30.days)..Time.zone.today)
).
group('people.id')
people.first.sales_amount # prints sales amount
如您所见,您可以将所有这些转换为您的with_sales_volume
方法。
答案 2 :(得分:1)
尝试一下:
@people = Person.all.includes(:payments).where(payments: { date: range }).sort_by { |p| p.payments.sum(&:amount) }
这个想法是将范围条件直接放入渴望的负载关联中,并更改求和的方式,以避免再次运行查询。