ActiveRecord查询对多个关联表

时间:2017-02-26 23:36:21

标签: sql ruby-on-rails activerecord

我正在使用RoR 4.2.4尝试创建一个索引页面,其中每一行显示一些买方信息,三个值从关联表中求和。我觉得这种查询必须一直发生,我只是错过了一些非常简单的事情。

class Buyer < ActiveRecord::Base
  has_many :wins
  has_many :pledges
  has_many :payments

胜利,承诺和付款属于买方。

这有效:

  @buyers = Buyer.joins("LEFT OUTER JOIN pledges on buyers.id = pledges.buyer_id")
                 .where(event_id: @event.id)
                 .select("buyers.*, sum(pledges.amount) as pledges_total")
                 .group('buyers.id')
                 .order('buyers.last_name')

我得到了买家和买家名单.pledges_total给了我们承诺的总和。

但在同一个查询中,我还希望获得总和(wins.amount)和sum(payments.amount)。

这个DOESN&#39; T工作:

  @buyers = Buyer.joins("LEFT OUTER JOIN wins on buyers.id = wins.buyer_id")
                 .joins("LEFT OUTER JOIN pledges on buyers.id = pledges.buyer_id")
                 .joins("LEFT OUTER JOIN payments on buyers.id = payments.buyer_id")
                 .where(event_id: @event.id)
                 .select("buyers.*, sum(wins.price) as wins_total, sum(pledges.amount) as pledges_total, sum(payments.amount) as payments_total")
                 .group('buyers.id')
                 .order('buyers.last_name')

我得到了奇怪的错误值,让我觉得我在加入后总结。但我真的不确定,我也不知道怎么做。我确信我真的很惊讶这是多么简单。

感谢您的帮助。

1 个答案:

答案 0 :(得分:1)

对于您拥有的多个JOIN,SUM将无法正常工作。尝试将该操作移动到子查询中,而不是对整个结果集进行求和。

@buyers = Buyer
  .where(event_id: @event.id)
  .select(<<-SELECT)
    buyers.*, 
    (SELECT SUM(wins.price) FROM wins WHERE wins.buyer_id = buyers.id) as wins_total, 
    (SELECT SUM(pledges.amount) FROM pledges WHERE pledges.buyer_id = buyers.id) as pledges_total, 
    (SELECT SUM(payments.amount) FROM payments WHERE payments.buyer_id = buyers.id) as payments_total
  SELECT
  .group('buyers.id')
  .order('buyers.last_name')

请注意,不再需要原始查询中的JOIN。

更新

以下是在查询中有多个JOIN时,为什么SUM不正确的说明。

假设您有以下数据:

# SELECT * FROM buyers;
 id  │ event_id
─────┼───────────
   1 │        1

# SELECT * FROM wins;
 id  │ buyer_id │ price
─────┼──────────┼───────
   2 │        1 │    10
   3 │        1 │    20

# SELECT * FROM pledges;
 id  │ buyer_id │ amount
─────┼──────────┼────────
   4 │        1 │     30
   5 │        1 │     40

SQL join返回给定记录集的笛卡尔积。这意味着连接的结果可能包含来自单个元组(行)的重复值。在下面的示例中,我们可以看到赢得的每个元组和承诺重复两次。 SQLFiddle

# SELECT buyers.id, wins.id AS wins_id, wins.price AS wins_price, pledges.id AS pledges_id, pledges.amount AS pledges_amount FROM buyers 
# INNER JOIN wins ON wins.buyer_id = buyers.id
# INNER JOIN pledges ON pledges.buyer_id = buyers.id;
 id  │ wins_id │ wins_price │ pledges_id │ pledges_amount
─────┼─────────┼────────────┼────────────┼────────────────
   1 │       2 │         10 │          4 │             30
   1 │       2 │         10 │          5 │             40
   1 │       3 │         20 │          4 │             30
   1 │       3 │         20 │          5 │             40

我们可以轻松回顾 wins 承诺表,看看赢取价格的总和等于30,认捐总额等于70.但是,如果我们按ID(buyer.id)分组并执行总和,然后我们最终得到的错误值是它们应该是的两倍! SQLFiddle

# SELECT buyers.id, sum(wins.price) AS wins_total, sum(pledges.amount) AS pledges_total FROM buyers 
# INNER JOIN wins ON wins.buyer_id = buyers.id
# INNER JOIN pledges ON pledges.buyer_id = buyers.id
# GROUP BY buyers.id;
 id  │ wins_total │ pledges_total
─────┼────────────┼───────────────
   1 │         60 │           140

您可以看到使用子选项会在此SQLFiddle中返回正确的结果。

后续

  

在您需要总结相关表的值时,是不是会出现这种情况?

是的,这是一个常见的问题。

  

这是人们做的吗?

我做到了。 :)

  

或者是否有一种更聪明的完全不同的方法?   只要您拥有良好的索引,子查询方法就可以很好地处理大量数据。在像这样的子查询成为一个重大问题之前,您可能会遇到其他性能问题。

但是,作为计算每个查询的价格和金额总和的替代方法,您可以缓存每个买方的总计值。对该确切主题进行了快速搜索this SO question。缓存增加了复杂性,有时可能很难。您需要评估是否真的需要来缓存值以及是否值得付出努力。我向您介绍的问题显示了如何使用ActiveRecord进行缓存。也可以在数据库中设置执行相同操作的触发器(可能更有效)。