1

在我的应用程序中,Invoice has_many item_numbers并且Invoice has_many payments. 每张发票都有一个余额,它是 ItemNumber 金额属性的总和,减去 Payment amount 属性的总和。

余额在发票模型中很容易计算,但我正在尝试编写一个按余额对发票进行排序的查询,而这在 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}")/

这个查询有两个问题。首先,它非常丑陋,其次,它不起作用。我在付款表上使用了完整的外部联接,因为并非所有发票都有付款,如果我只使用 joins(:payments) 任何没有付款的发票,都会从结果中排除。COALESCE 被放置在那里以处理空金额。

查询接近,但假设有 3 个 item_numbers 和 1 个付款(非常典型的场景),付款金额将被减去 3 次,导致余额远低于实际金额(通常为负余额)。

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

4

2 回答 2

3

不确定 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有一个invoice_id类似的列payments,我对其进行了修改。

于 2013-01-01T11:26:14.243 回答
2

您的问题是由列相乘引起的。想象一下,有一个 Payment 和三个 Item_numbers 属于 Invoice。常规连接的结果是这样的:

| 发票.id | item_number.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')
于 2013-01-01T12:08:59.500 回答