创建rails SUM查询对象

时间:2014-08-24 23:50:07

标签: mysql sql ruby-on-rails ruby activerecord

在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描述了如何使用查询对象。但我不明白如何在我的案例中创建一个。

1 个答案:

答案 0 :(得分:1)

尝试使用select这样的游戏,您应该在单个查询中拥有所需内容。

@accounts = Account.all.joins(:transactions).
  select("accounts.*, SUM(transactions.price) AS balance").
  group("accounts.id")

此查询的唯一问题是您只会获得实际拥有交易的帐户。没有交易的人将被排除在外。

发生了什么

第一部分Account.all.joins(:transactions)accountstransactions表连接在一起。 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 JOINWhat is the difference between "INNER JOIN" and "OUTER JOIN"?