在我的应用中,Invoice has_many item_numbers
和Invoice 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。
答案 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')