在DB中有很多帐户,每个帐户都有很多带有price属性的交易。需要通过汇总所有交易价格来计算账户余额。
class AccountsController < ApplicationController
def index
@accounts = Account.all
end
end
帐户模型:
class Account < ActiveRecord::Base
has_many :transactions, dependent: :destroy
includes :transactions
def balance
transactions.sum(:price) || 0
end
def balance_ruby_sum
sum = 0
transactions.each { |t| sum+= t.price }
sum
end
end
交易模式:
class Transaction < ActiveRecord::Base
belongs_to :account
end
帐户index.html.erb:
<table>
<th>name</th>
<th>balance</th>
<% @accounts.each do |acc| %>
<td><%= acc.name %></td>
<td><%= acc.balance %></td>
<% end %>
</table>
在索引视图中,我有帐号的名称及其余额。
需要向DB发起N + 1个查询才能执行此操作,我想避免N + 1问题。
我尝试使用balance_ruby_sum
操作而不是balance
操作(只需要2个查询),但这不是一个好的解决方案(性能问题)。
我不想要检索所有完整交易,只检查它们的价格总和。是否可以使用SQL按总价计算余额,并且只进行2次查询?
我在4项中找到了article描述了如何使用查询对象。但我不明白如何在我的案例中创建一个。
答案 0 :(得分:1)
尝试使用select
这样的游戏,您应该在单个查询中拥有所需内容。
@accounts = Account.all.joins(:transactions).
select("accounts.*, SUM(transactions.price) AS balance").
group("accounts.id")
此查询的唯一问题是您只会获得实际拥有交易的帐户。没有交易的人将被排除在外。
发生了什么
第一部分Account.all.joins(:transactions)
将accounts
和transactions
表连接在一起。 ActiveRecord
非常聪明,知道我们在事务中使用account_id
来加入表格。
接下来,我们在此联接表中想要的是帐户信息,但我们想要一个“额外字段”,它是所有帐户交易价格的总和。我们基本上想要:
+-------------+-----+-----+---------+
| account id | ... | ... | balance |
+-------------+-----+-----+---------+
这就是我们需要select
声明select("accounts.*, SUM(transactions.price) AS balance")
的原因。我们告诉ActiveRecord
在此联接表中为我们提供所有帐户属性,并建立一个特殊的balance
,即与此帐户关联的所有交易价格的总和。< / p>
最后,我们需要给ActiveRecord
一个关于如何组织结果的线索,并告诉它按帐户ID对记录进行分组。在我们的情况下,每行一个帐户。
<强> balance_ruby_sum 强>
如果您想保留balance_ruby_sum
方法,可以考虑使用inject
这样的方法:
def balance_ruby_sum
transactions.inject(0) { |sum, t| sum + t.price }
end
你不会获得性能,但它更像rubyesque:)
当评论中没有要求的交易时,编辑返回0
即使没有附加任何交易,也要确保每个帐户获得一行。我们需要强制LEFT OUTER JOIN
而不是INNER JOIN
。
现在SUM
,当没有事务时,将返回NULL
作为余额,我们希望它返回0.当没有事务时返回0的方法是使用{{1围绕COALESCE
的关键字。 SUM
用于在第一个参数为COALESCE
时返回默认值。您的请求现在看起来像这样:
NULL
您可以在此处详细了解@accounts = Account.all.
joins("LEFT OUTER JOIN transactions ON accounts.id = transactions.account_id").
select("accounts.*, COALESCE(SUM(transactions.price), 0) as balance").
group("accounts.id")
和INNER JOIN
:
What is the difference between "INNER JOIN" and "OUTER JOIN"?