web-dev-qa-db-ja.com

SQL JOIN、GROUP BYで合計を取得する3つのテーブル

次のDB設計を継承しました。テーブルは次のとおりです。

customers
---------
customerid  
customernumber

invoices
--------
invoiceid  
amount

invoicepayments
---------------
invoicepaymentid  
invoiceid  
paymentid

payments
--------
paymentid  
customerid  
amount

私のクエリは、指定された顧客番号に対して、請求書ID、請求書金額(請求書テーブル内)、および支払額(請求書金額から請求書に対して行われた支払いを差し引いたもの)を返す必要があります。顧客は複数の請求書を持っている場合があります。

次のクエリは、請求書に対して複数の支払いが行われたときに重複したレコードを提供します。

SELECT i.invoiceid, i.amount, i.amount - p.amount AS amountdue
FROM invoices i
LEFT JOIN invoicepayments ip ON i.invoiceid = ip.invoiceid
LEFT JOIN payments p ON ip.paymentid = p.paymentid
LEFT JOIN customers c ON p.customerid = c.customerid
WHERE c.customernumber = '100'

どうすれば解決できますか?

12
Nick

私はあなたを手に入れたかはわかりませんが、これはあなたが探しているものかもしれません:

SELECT i.invoiceid, sum(case when i.amount is not null then i.amount else 0 end), sum(case when i.amount is not null then i.amount else 0 end) - sum(case when p.amount is not null then p.amount else 0 end) AS amountdue
FROM invoices i
LEFT JOIN invoicepayments ip ON i.invoiceid = ip.invoiceid
LEFT JOIN payments p ON ip.paymentid = p.paymentid
LEFT JOIN customers c ON p.customerid = c.customerid
WHERE c.customernumber = '100'
GROUP BY i.invoiceid

これにより、各請求書に複数の支払い行がある場合の金額の合計が得られます

14
Saggi Malachi

返信ありがとうございました!

Saggi Malachi、このクエリは、残念ながら複数の支払いがある場合の請求額を合計します。 18ドルと12ドルの39ドルの請求書に対して2回の支払いがあるとします。結果は次のようになります:

1   39.00   9.00

次のようになります。

1   78.00   48.00

Charles Bretanaは、クエリを可能な限り単純なクエリにトリミングする過程で、顧客と請求書間のリンクを提供する追加のテーブルcustomerinvoicesを(愚かに)省略しました。これは、支払いが行われていない請求書を確認するために使用できます。

多くの苦労の末、次のクエリは必要なものを返すと思います。

SELECT DISTINCT i.invoiceid, i.amount, ISNULL(i.amount - p.amount, i.amount) AS amountdue
FROM invoices i
LEFT JOIN invoicepayments ip ON i.invoiceid = ip.invoiceid
LEFT JOIN customerinvoices ci ON i.invoiceid = ci.invoiceid
LEFT JOIN (
  SELECT invoiceid, SUM(p.amount) amount
  FROM invoicepayments ip 
  LEFT JOIN payments p ON ip.paymentid = p.paymentid
  GROUP BY ip.invoiceid
) p
ON p.invoiceid = ip.invoiceid
LEFT JOIN payments p2 ON ip.paymentid = p2.paymentid
LEFT JOIN customers c ON ci.customerid = c.customerid
WHERE c.customernumber='100'

同意しますか?

6

同じテーブルからさまざまな集計値を取得したい人のためのヒントがあります。

ユーザーのテーブルとユーザーが獲得したポイントのテーブルがあるとしましょう。したがって、それらの間の接続は1:N(1人のユーザー、多くのポイントレコード)です。

ここで、「ポイント」テーブルに、ユーザーがポイントを獲得した理由(ログイン、バナーをクリックなど)に関する情報も保存します。そして、SUM(points) AND SUM(points WHERE type = x)の順に並べられたすべてのユーザーをリストしたいと思います。つまり、ユーザーが持つすべてのポイントで並べ替えられ、特定のアクション(ログインなど)でユーザーが獲得したポイントで並べ替えられます。

SQLは次のようになります。

_SELECT SUM(points.points) AS points_all, SUM(points.points * (points.type = 7)) AS points_login
FROM user
LEFT JOIN points ON user.id = points.user_id
GROUP BY user.id
_

これの美しさはSUM(points.points * (points.type = 7))にあり、内側の括弧が0または1のいずれかに評価されるため、指定されたポイント値に0または1を掛けます。

2
PunchyRascal

まず、InvoicesテーブルにCustomerIdを含めるべきではありませんか?現状では、まだ支払いのない請求書に対してこのクエリを実行することはできません。請求書に支払いがない場合、その請求書は外部結合であってもクエリの出力に表示されません...

また、顧客が支払いを行うとき、どの請求書を添付するのかをどのように知るのですか?支払いとともに到着するスタブ上のInvoiceIdによる唯一の方法である場合、請求書を注文した顧客ではなく、支払った顧客に(おそらく不適切に)関連付けています。 (請求書は、サービスを注文した顧客以外の誰かが支払う場合があります)

2
Charles Bretana

これは遅いことはわかっていますが、元の質問には答えています。

/*Read the comments the same way that SQL runs the query
    1) FROM 
    2) GROUP 
    3) SELECT 
    4) My final notes at the bottom 
*/
SELECT 
        list.invoiceid
    ,   cust.customernumber 
    ,   MAX(list.inv_amount) AS invoice_amount/* we select the max because it will be the same for each payment to that invoice (presumably invoice amounts do not vary based on payment) */
    ,   MAX(list.inv_amount) - SUM(list.pay_amount)  AS [amount_due]
FROM 
Customers AS cust 
    INNER JOIN 
Payments  AS pay 
    ON 
        pay.customerid = cust.customerid
INNER JOIN  (   /* generate a list of payment_ids, their amounts, and the totals of the invoices they billed to*/
    SELECT 
            inpay.paymentid AS paymentid
        ,   inv.invoiceid AS invoiceid 
        ,   inv.amount  AS inv_amount 
        ,   pay.amount AS pay_amount 
    FROM 
    InvoicePayments AS inpay
        INNER JOIN 
    Invoices AS inv 
        ON  inv.invoiceid = inpay.invoiceid 
        INNER JOIN 
    Payments AS pay 
        ON pay.paymentid = inpay.paymentid
    )  AS list
ON 
    list.paymentid = pay.paymentid
    /* so at this point my result set would look like: 
    -- All my customers (crossed by) every paymentid they are associated to (I'll call this A)
    -- Every invoice payment and its association to: its own ammount, the total invoice ammount, its own paymentid (what I call list) 
    -- Filter out all records in A that do not have a paymentid matching in (list)
     -- we filter the result because there may be payments that did not go towards invoices!
 */
GROUP BY
    /* we want a record line for each customer and invoice ( or basically each invoice but i believe this makes more sense logically */ 
        cust.customernumber 
    ,   list.invoiceid 
/*
    -- we can improve this query by only hitting the Payments table once by moving it inside of our list subquery, 
    -- but this is what made sense to me when I was planning. 
    -- Hopefully it makes it clearer how the thought process works to leave it in there
    -- as several people have already pointed out, the data structure of the DB prevents us from looking at customers with invoices that have no payments towards them.
*/
0
Edward