通过两个“has_many”子表的总和来排序查询?

时间:2013-01-01 09:56:45

标签: sql ruby-on-rails postgresql activerecord join

在我的应用中,Invoice has_many item_numbersInvoice has_many payments。每张发票都有一个余额,即ItemNumber金额属性的总和,减去付款金额属性的总和。

在发票模型中很容易计算余额,但我正在尝试编写一个按余额对发票进行排序的查询,这在ActiveRecord / SQL中更难实现。

我已成功设法使用以下查询在item_numbers的总数上订购发票(感谢Daniel Rikowski):

Invoice.where(user_id: 1, deleted: false, status: 'Sent')
       .joins(:item_numbers)
       .select('invoices.*, sum(item_numbers.amount)')
       .group('invoices.id')
       .order('sum(item_numbers.amount) asc')
       .limit(20)

我试图通过以下余额将其扩展到订单;

Invoice.where(user_id: 1, deleted: false, status: 'Sent')
       .joins(:item_numbers)
       .joins("FULL OUTER JOIN payments ON payments.invoice_id = invoices.id")
       .select("invoices.*, sum(item_numbers.amount_with_gst) - COALESCE(sum(payments.amount), 0)")
       .group("invoices.id")
       .order("sum(item_numbers.amount_with_gst) - COALESCE(sum(payments.amount), 0) #{dir}")/

此查询存在两个问题。首先,它非常丑陋,其次,它不起作用。我在付款表上使用了完全外部联接,因为并非所有发票都有付款,如果我只使用加入(:付款),则任何没有付款的发票都会从结果中排除。 COALESCE被用来处理零金额。

查询接近,但是说有3个item_numbers和1个​​付款(一个非常典型的情况),付款金额将减去3次,导致余额远低于实际金额(通常是负余额)。

我可能很清楚我的深度。我已经在这个查询中付出了很多努力(大约4个小时的阅读和失败的尝试)并且不能完全确定它。我的数据库是PostgreSQL。

2 个答案:

答案 0 :(得分:2)

不确定AR语法,但正确的查询是:

SELECT i.*, COALESCE(n.total, 0) - COALESCE(p.total, 0) AS balance
FROM   invoices i
LEFT   JOIN (
    SELECT invoice_id, sum(amount) AS total
    FROM   payments
    GROUP  BY invoice_id 
    ) p ON p.invoice_id = i.id
LEFT   JOIN (
    SELECT invoice_id, sum(amount_with_gst) AS total
    FROM   item_numbers
    GROUP  BY invoice_id 
    ) n ON n.invoice_id = i.id
WHERE  i.user_id = 1
AND    i.deleted = false
AND    i.status = 'Sent'
ORDER  BY balance;

如果将两个has_many表连接到基表,则行相互相乘会导致完全任意的结果。您可以通过在加入基表之前聚合总计来解决这个问题。

另外,我在查询中没有看到item_numbers的加入条件。这将导致交叉连接 - 除了非常错误之外极其昂贵。 (或者AR是否足够聪明,可以自动从外键关系派生连接条件?如果是,为什么第二个表上的连接条件?)假设 item_numbers有一个{{1 }}就像invoice_id一样,我对其进行了修改。

答案 1 :(得分:2)

您的问题是由列乘以引起的。想象一下,有一个付款和三个属于发票的Item_numbers。常规联接的结果将是这样的:

| invoice.id | item_number.amount | payment.amount |
| 1          | 4                  | 5              |
| 1          | 7                  | 5              |
| 1          | 2                  | 5              |

因此,sum(payment.amount)将返回15而不是5.要获得正确的金额,您必须直接获取金额:

Invoice.select('invoices.id, (SELECT SUM(item_numbers.amount) from item_numbers WHERE item_numbers.invoice_id = invoices.id) - (SELECT COALESCE(SUM(payments.amount),0) from payments WHERE payments.invoice_id = invoices.id) AS balance').group('invoices.id')