2013-01-01 79 views
1

在我的應用程序中,Invoice has_many item_numbersInvoice has_many payments。每張發票都有一個餘額,它是ItemNumber金額屬性的總和,減去付款金額屬性的總和。通過兩個「has_many」子表的總和來排序查詢?

發票模型中的餘額很容易計算,但我試圖編寫一個查詢來平衡發票排序,這在ActiveRecord/SQL中很難做到。

我成功設法訂單上的總ITEM_NUMBERS與下面的查詢(感謝丹尼爾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}")/ 

該查詢有兩個問題。首先,它非常醜陋,其次,它不起作用。由於並非所有發票都有付款,並且如果我僅使用聯合(:付款),所以我在付款表上使用了完整的外部聯接,因此從結果中排除了沒有付款的任何發票。聯盟被放置在那裏來處理零金額。

查詢接近,但說有3個item_numbers和1個​​付款(一個非常典型的情況),付款金額將減去3次,導致餘額遠小於實際金額(通常爲負餘額) 。

這可能很清楚我的深度。我爲這個查詢付出了很多努力(大約4個小時的閱讀和失敗的嘗試),並不能完全明白它。我的數據庫是PostgreSQL。

回答

2

您的問題是由列倍增造成的。設想一個支付和三個Item_number屬於發票。常規連接的結果會是這樣的:

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

正因爲如此,和(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') 
+0

這個功能非常完美,並且具有令人愉快理解的額外優勢。我甚至修改了一些現有的搜索查詢以相同的方式排序。 .group('invoices.id')似乎不再是必要的,但(可能是因爲沒有連接)。謝謝。 – brad

+0

@brad:請注意,對於結果中的多個行,相關的子查詢通常比我演示的(''LEFT')'JOIN'慢得多。有人甚至稱這是一種反模式。用['EXPLAIN ANALYSE']測試不同之處(http://www.postgresql.org/docs/current/interactive/sql-explain.html)。 –

+0

謝謝你的擡頭。我會做更多的調查。我的數據庫非常小,我的查詢也很小,所以它不應該影響我太多(現在....)。 – brad

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有一個invoice_id列,就像payments那樣,我修改了這個。

+0

謝謝非常。如果我將它放入Invoice.find_by_sql()中,您的答案確實有效。在item_numbers上沒有連接條件,因爲AR在這種情況下進行內部連接(Invoice.joins(:item_numbers)=> SELECT「invoices」。* FROM「invoices」INNER JOIN「item_numbers」ON「item_numbers」。「invoice_id」= 「發票」, 「ID」)。我選擇了另一個答案,因爲它包含了ActiveRecord解決方案,這是我在代碼中使用的。然而,你的回答很有教育意義。 – brad