it-swarm.com.de

SQL JOIN, GROUP BY für drei Tabellen, um Gesamtwerte zu erhalten

Ich habe das folgende DB-Design geerbt. Tabellen sind:

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

invoices
--------
invoiceid  
amount

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

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

Meine Anfrage muss rechnungs-ID, den Rechnungsbetrag (in der Rechnungstabelle) und den fälligen Betrag (Rechnungsbetrag abzüglich der an die Rechnung geleisteten Zahlungen) für eine bestimmte Kundennummer zurückgeben. Ein Kunde kann mehrere Rechnungen haben.

Die folgende Abfrage liefert doppelte Datensätze, wenn mehrere Zahlungen auf eine Rechnung erfolgen:

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'

Wie kann ich das lösen?

10
Nick

Ich bin nicht sicher, ob ich dich gefunden habe, aber vielleicht suchst du danach:

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

Dies würde Ihnen die Summensummen liefern, falls für jede Rechnung mehrere Zahlungszeilen vorhanden sind

13
Saggi Malachi

Vielen Dank für die Antworten! 

Saggi Malachi, diese Abfrage summiert leider den Rechnungsbetrag bei mehreren Zahlungen. Angenommen, es gibt zwei Zahlungen für eine $ 39-Rechnung von $ 18 und $ 12. Anstatt mit einem Ergebnis zu enden, das wie folgt aussieht:

1   39.00   9.00

Sie werden am Ende mit:

1   78.00   48.00

Charles Bretana, während ich meine Anfrage auf eine möglichst einfache Anfrage reduzierte, ließ ich (dumm) eine zusätzliche Tabelle, customerinvoices, weg, die eine Verbindung zwischen Kunden und Rechnungen herstellt. Hiermit können Rechnungen angezeigt werden, für die noch keine Zahlungen geleistet wurden.

Nach langem Kämpfen denke ich, dass die folgende Abfrage das ergibt, wozu ich es brauche:

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'

Würden Sie sich einig sein?

5

Ich habe einen Tipp für diejenigen, die verschiedene aggregierte Werte aus derselben Tabelle erhalten möchten. 

Nehmen wir an, ich habe eine Tabelle mit Benutzern und eine Tabelle mit Punkten, die die Benutzer erwerben. Die Verbindung zwischen ihnen ist also 1: N (ein Benutzer, viele Punktdatensätze).

Jetzt speichere ich in der Tabelle "Punkte" auch die Informationen darüber, für was der Benutzer die Punkte erhalten hat (Login, Klicken auf ein Banner usw.). Und ich möchte alle Benutzer auflisten, die nach SUM(points) UND dann nach SUM(points WHERE type = x) bestellt wurden. Das heißt, nach allen Punkten, die der Benutzer hat, und dann nach Punkten, die der Benutzer für eine bestimmte Aktion erhalten hat (z. B. Login).

Die SQL wäre:

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

Das Schöne daran ist in SUM(points.points * (points.type = 7)), wo die innere Klammer entweder 0 oder 1 ergibt und den angegebenen Punktewert mit 0 oder 1 multipliziert, je nachdem, welcher Punkt gleich dem gewünschten Punktetyp ist.

2
PunchyRascal

Sollte es in der Tabelle "Rechnungen" nicht eine CustomerId geben? Sie können diese Abfrage für Rechnungen, für die noch keine Zahlungen vorgenommen wurden, nicht durchführen. Wenn auf einer Rechnung keine Zahlungen erfolgen, wird diese Rechnung nicht einmal in der Ausgabe der Abfrage angezeigt, obwohl es sich um eine äußere Verknüpfung handelt. 

Wenn ein Kunde eine Zahlung vornimmt, woher wissen Sie dann, an welche Rechnung er angehängt werden soll? Wenn die InvoiceId auf dem Stub, der bei der Zahlung eingeht, der einzige Weg ist, verbinden Sie (möglicherweise unangemessen) Rechnungen mit dem Kunden, der sie bezahlt hat, und nicht mit dem Kunden, der sie bestellt hat .... (Manchmal kann eine Rechnung von einer anderen Person als dem Kunden bezahlt werden, der die Dienstleistungen bestellt hat.) 

2
Charles Bretana

Ich weiß, dass dies zu spät ist, aber es beantwortet Ihre ursprüngliche Frage. 

/*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