SQL JOIN, GROUP BY на три таблицы, чтобы получить итоги - PullRequest
12 голосов
/ 07 августа 2009

Я унаследовал следующий дизайн БД. Таблицы:

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

invoices
--------
invoiceid  
amount

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

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

Мой запрос должен вернуть счет-фактуру, сумму счета-фактуры (в таблице счетов-фактур) и подлежащую оплате сумму (сумма счета-фактуры за вычетом любых платежей, выполненных по счету-фактуре) для данного номера клиента. У клиента может быть несколько счетов.

Следующий запрос дает мне дубликаты записей, когда в счет-фактуру делается несколько платежей:

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'

Как я могу решить это?

Ответы [ 5 ]

14 голосов
/ 07 августа 2009

Я не уверен, что получил вас, но это может быть то, что вы ищете:

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

Это даст вам суммы сумм в случае, если для каждого счета-фактуры есть несколько строк оплаты

6 голосов
/ 07 августа 2009

Большое спасибо за ответы!

Saggi Malachi, этот запрос, к сожалению, суммирует сумму счета в тех случаях, когда существует более одного платежа. Скажем, есть два платежа по счету за 39 долларов в размере 18 и 12 долларов. Так что вместо того, чтобы в итоге получить результат, который выглядит следующим образом:

1   39.00   9.00

Вы получите:

1   78.00   48.00

Чарльз Бретана, в процессе урезания моего запроса до самого простого запроса, я (тупо) пропустил дополнительную таблицу 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'

Ребята, согласитесь?

2 голосов
/ 22 июня 2010

У меня есть совет для тех, кто хочет получить различные агрегированные значения из одной таблицы.

Допустим, у меня есть таблица с пользователями и таблица с очками, которые получают пользователи. Таким образом, связь между ними составляет 1: N (один пользователь, много точек записей).

Теперь в таблице «Баллы» я также храню информацию о том, за что пользователь получил баллы (логин, нажатие на баннер и т. Д.). И я хочу перечислить всех пользователей, упорядоченных по SUM(points) И затем по 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 голосов
/ 07 августа 2009

Прежде всего, не должно ли быть CustomerId в таблице Invoices? Таким образом, вы не можете выполнить этот запрос для счетов-фактур, по которым еще нет платежей. Если в счете нет платежей, этот счет даже не будет отображаться в выходном запросе, даже если это внешнее соединение ...

Кроме того, когда клиент совершает платеж, как вы узнаете, к какому счету его следует прикрепить? Если единственный способ заключается в использовании InvoiceId для квитанции, которая поступает с платежом, то вы (возможно, ненадлежащим образом) связываете счета с клиентом, который их оплатил, а не с клиентом, который их заказал ... (Иногда счет может быть оплачен кем-то, кроме клиента, который заказал услуги)

0 голосов
/ 28 июня 2018

Я знаю, что уже поздно, но это отвечает на ваш первоначальный вопрос.

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