SQL JOIN, GROUP BY для трех таблиц, чтобы получить итоги

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

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

invoices
--------
invoiceid  
amount

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

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

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

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

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'

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


person Community    schedule 06.08.2009    source источник
comment
сколько строк оплаты счетов может существовать для одного счета? сколько платежей может существовать для каждого идентификатора платежа?   -  person grateful.dev    schedule 07.08.2009


Ответы (5)


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

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

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

person grateful.dev    schedule 06.08.2009
comment
Вероятно, вам следует обернуть столбцы amount столбцами coalesce или isnull; с MS-SQL любые значения NULL, например. из left joins приведет к тому, что все это будет оценено как NULL. Я знаю, что некоторые диалекты SQL допускают NULL == 0, но это приводит к религиозной войне, так что... - person Adrien; 07.08.2009
comment
Вы правы, я добавил пункты case, так как не уверен, какую базу данных SQL он использует. Спасибо! - person grateful.dev; 07.08.2009
comment
Лично я не стал бы СУММИЗИРОВАТЬ суммы счетов, предполагая, что invoiceid является первичным ключом и что сумма в таблице счетов ограничена значением NOT NULL, а вместо этого добавлял i.amount в предложение GROUP BY. - person Dave Costa; 07.08.2009

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

Сагги Малачи, этот запрос, к сожалению, суммирует сумму счета в случаях, когда существует более одного платежа. Скажем, есть два платежа по счету на 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'

Согласны ли вы, ребята?

person Nick Silberstein    schedule 07.08.2009

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

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

person Charles Bretana    schedule 06.08.2009
comment
Это действительно хорошие моменты; хотя я бы сказал, что ваше последнее утверждение - это бизнес-правило, которое может быть верным или неверным в компании автора. В целом, дизайн беспорядочный, и нет смысла использовать внешние соединения, так как предикат требует, чтобы от Customers возвращалась строка. - person Dave Costa; 07.08.2009

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

Допустим, у меня есть таблица с пользователями и таблица с баллами, которые набирают пользователи. Таким образом, связь между ними 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, в зависимости от того, соответствует ли оно типу очков, который мы хотим.

person PunchyRascal    schedule 22.06.2010

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

/*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.
*/
person Edward    schedule 28.06.2018