MySQL JOIN with multiple tables and SUMS - mysql

I am trying to create a query that will take information out of four tables for a billing system that I am creating. I have the following tables:
Table Invoice
InvoiceID (PK)
ClientID
Date
Status
...
Table Client
ClientID (PK)
ClientName
...
Table InvoiceItem
ItemID (PK)
InvoiceID
Amount
...
Table Payments
PaymentID (PK)
InvoiceID
Amount
...
I need to create a query where I can access information from the Invoice table along with the client name, and the sum of all invoice items and payments associated with the invoice.
I have tried the following:
SELECT
Invoice.InvoiceID,
Invoice.`Date`,
Invoice.Terms,
Invoice.DateDue,
Invoice.Status,
Client.ClinicName,
SUM(InvoiceItem.Amount),
SUM(Payment.PaymentAmount)
FROM Invoice
JOIN (Client, InvoiceItem, Payment) ON
(Client.ClientID=Invoice.ClientID AND
InvoiceItem.InvoiceID=Invoice.InvoiceID AND
Payment.InvoiceID=Invoice.InvoiceID)
And while this kind-of works, it is multiplying the SUM() by the number of records used to get the sum (i.e. if there are two payments - 800,400 - It gives me (800+400)*2 -- 2400). I am guessing that there is something with how I am using the join, and I have honestly never had to use join for more than one table, and I would always use GROUP BY, but I can't seem to get that to work correctly.
To make matters worse, I have been lost to the world of vb.net/MSSQL client-side programming for the past several years, so my MySQL is rather rough.

Your problem is that you can't aggregate over two independent tables at once in a single query. However you can do it using subqueries.
SELECT Invoice.InvoiceID, Invoice.`Date`, Invoice.Terms, Invoice.DateDue, Invoice.Status, Client.ClinicName, InvoiceItemSum.SumOfAmount, PaymentSum.SumOfPaymentAmount
FROM Invoice
INNER JOIN Client ON Client.ClientID = Invoice.ClientID
INNER JOIN (
SELECT InvoiceID, SUM(Amount) AS SumOfAmount
FROM InvoiceItem
GROUP BY InvoiceID
) InvoiceItemSum ON InvoiceItemSum.InvoiceID = Invoice.InvoiceID
INNER JOIN (
SELECT InvoiceID, SUM(PaymentAmount) AS SumOfPaymentAmount
FROM Payment
GROUP BY InvoiceID
) PaymentSum ON PaymentSum.InvoiceID = Invoice.InvoiceID

Here try this one
SELECT a.InvoiceID,
a.`Date`,
a.Terms,
a.DateDue,
a.Status,
b.ClinicName,
SUM(c.Amount),
SUM(d.PaymentAmount)
FROM Invoice a
INNER JOIN Client b
on a.ClientID = b.ClientID
INNER JOIN InvoiceItem c
ON c.InvoiceID = a.InvoiceID
INNER JOIN JOIN Payment d
ON d.InvoiceID = a.InvoiceID
GROUP BY a.InvoiceID,
a.`Date`,
a.Terms,
a.DateDue,
a.Status,
b.ClinicName
can you elaborate more on this?
it is multiplying the SUM() by the number of records used to get the
sum (i.e. if there are two payments - 800,400 - It gives me
(800+400)*2 -- 2400)

Try this:
SELECT
Invoice.InvoiceID,
Invoice.`Date`,
Invoice.Terms,
Invoice.DateDue,
Invoice.Status,
Client.ClinicName,
SUM(InvoiceItem.Amount),
SUM(Payment.PaymentAmount)
FROM Invoice
JOIN Client ON Client.ClientID=Invoice.ClientID
JOIN InvoiceItem ON InvoiceItem.InvoiceID=Invoice.InvoiceID
JOIN Payment ON Payment.InvoiceID=Invoice.InvoiceID
group by 1,2,3,4,5,6;
I did two things to your query:
Created separated joins for each of the child tables
Added a group by, without which the sum won't work correctly (fyi, in all other databases, omitting the group by would actually result in a syntax error)

you can also achive it by "CROSS APPLY"
SELECT Invoice.InvoiceID, Invoice.`Date`, Invoice.Terms, Invoice.DateDue, Invoice.Status, Client.ClinicName, InvoiceItemSum.SumOfAmount, PaymentSum.SumOfPaymentAmount
FROM Invoice
INNER JOIN Client ON Client.ClientID = Invoice.ClientID
CROSS APPLY ( SELECT ISNULL(SUM(Amount),0) AS SumOfAmount
FROM InvoiceItem
WHERE InvoiceID = Invoice.InvoiceID
) InvoiceItemSum
CROSS APPLY ( SELECT ISNULL(SUM(PaymentAmount),0) AS SumOfPaymentAmount
FROM Payment
WHERE InvoiceID = Invoice.InvoiceID
) PaymentSum

Related

Get the sum of MySQL query result

I have a table BILLS with ID_CONTRACT_INST, F_SUM, DT_EVENT(Date) Columns
And another table CONTRACTS with ID_CONTRACT_INST(primary key), V_EXIT_IDENT
My TASK: sum of F_SUM column from table BILLS where
V_EXIT_IDENT from Table CONTRACTS = 1158
I get: All but the sum
select BILLS.ID_CONTRACT_INST, BILLS.F_SUM
from BILLS
where ID_CONTRACT_INST IN
(select ID_CONTRACT_INST from CONTRACTS
where V_EXIT_IDENT = 1158);
If BILLS.ID_CONTRACT_INST is unique then use
select BILLS.ID_CONTRACT_INST, SUM(BILLS.F_SUM) F_SUM
from BILLS
where ID_CONTRACT_INST IN (select ID_CONTRACT_INST
from CONTRACTS
where V_EXIT_IDENT = 1158)
GROUP BY BILLS.ID_CONTRACT_INST WITH ROLLUP;
In the output you will see additional row with BILLS.ID_CONTRACT_INST value of NULL and total sum in F_SUM column.
GROUP BY Modifiers
This appears to simply need:
select sum(BILLS.F_SUM)
from CONTRACTS
left join BILLS using (ID_CONTRACT_INST)
where V_EXIT_IDENT = 1158
Unless you meant separate sums for each contract, which is:
select CONTRACTS.ID_CONTRACT_INST, sum(BILLS.F_SUM)
from CONTRACTS
left join BILLS using (ID_CONTRACT_INST)
where V_EXIT_IDENT = 1158
group by CONTRACTS.ID_CONTRACT_INST
Note that if you only want to include results for contracts that have at least one bill, you would use inner join instead of left join. (In the case of the first query, this would return no result rows instead of a zero if there were no bills found for any selected contract.)
You need to apply the sum function on the values to be aggregated and group them by all your output table's unaggregated columns.
select BILLS.ID_CONTRACT_INST, sum(BILLS.F_SUM)
from BILLS
where ID_CONTRACT_INST IN
(select ID_CONTRACT_INST from CONTRACTS
where V_EXIT_IDENT = 1158)
group by BILLS.ID_CONTRACT_INST;
Alternatively, a left join works the same way:
select BILLS.ID_CONTRACT_INST, sum(BILLS.F_SUM)
from BILLS as b
left join CONTRACTS as c
ON b.ID_CONTRACT_INST = c.ID_CONTRACT_INST
where V_EXIT_IDENT = 1158
group by BILLS.ID_CONTRACT_INST;

Inner Join for 3 tables with SUM of two columns in SQL Query?

I have the following three tables:
I have Following Query to Join Above 3 Tables
customer.customer_id,
customer.name,
SUM(sales.total),
sales.created_at,
SUM(sales_payments.amount)
FROM
sales INNER JOIN customer ON customer.customer_id = sales.customer_id
INNER JOIN sales_payments ON sales.customer_id = sales_payments.customer_id
WHERE sales.created_at ='2020-04-03'
GROUP By customer.name
Result for Above Query is given below
Sum of sales.total is double of the actual sum of sales.total column which has 2-row count, I need to have the actual SUM of that column, without doubling the SUM of those rows, Thank you, for your help in advance..
PROBLEM
The problem here is that there are consecutive inner joins and the number of rows getting fetched in the second inner join is not restricted. So, as we have not added a condition on sales_payment_id in the join between the sales and sales_payment tables, one row in sales table(for customer_id 2, in this case) would be mapped to 2 rows in the payment table. This causes the same values to be reconsidered.
In other words, the mapping for customer_id 2 between the 3 tables is 1:1:2 rather than 1:1:1.
SOLUTION
Solution 1 : As mentioned by Gordon, you could first aggregate the amount values of the sales_payments table and then aggregate the values in sales table.
Solution 2 : Alternatively (IMHO a better approach), you could add a foreign key between sales and sales_payment tables. For example, the sales_payment_id column of sales_payment table can be introduced in the sales table as well. This would facilitate the join between these tables and reduce additional overheads while querying data.
The query would then look like:
`SELECT c.customer_id,
c.name,
SUM(s.total),
s.created_at,
SUM(sp.amount)
FROM customer c
INNER JOIN sales s
ON c.customer_id = s.customer_id
INNER JOIN sales_payments sp
ON c.customer_id = sp.customer_id
AND s.sales_payments_id = sp.sales_payments_id
WHERE s.created_at ='2020-04-03'
GROUP BY c.customer_id,
c.name,
s.created_at ;`
Hope that helps!
You have multiple rows for sales_payments and sales per customer. You need to pre-aggregate to get the right value:
SELECT c.customer_id, c.name, s.created_at, s.total, sp.amount
FROM customer c JOIN
(SELECT s.customer_id, s.created_at, SUM(s.total) as total
FROM sales s
WHERE s.created_at ='2020-04-03'
GROUP BY s.customer_id, s.created_at
) s
ON c.customer_id = s.customer_id JOIN
(SELECT sp.customer_id, SUM(sp.amount) as amount
FROM sales_payments sp
GROUP BY sp.customer_id
) sp
ON s.customer_id = sp.customer_id

Counting amount of base records in (My)Sql 1-to-many relation

I have these two tables:
invoices (contains id field)
contracts (contains fk to invoice + 'code' field)
Let's say I have one record in the invoices table and two records in the contracts table. Both records in the contracts table point to the same invoice record.
Desire: I'd like to count the amount of invoices.
What I've got so far:
select
c.code, count(*)
from
invoices i
join
contracts c
on
c.invoice_id = i.id
group by
c.code
Although the count shows two instead of the desired 1. I understand that this is because of the join on the contract table, but not sure how to fix this.
Try with COUNT(DISTINCT i.id); it should count the different invoice id's in the resultset.
select
c.code, count(distinct i.id)
from
invoices i
join
contracts c
on
c.invoice_id = i.id
group by
c.code
You want the number of invoices?
select count(*)
from invoices i
You want invoices with contracts?
select count( distinct c.invoice_id)
from
contracts
Your code finds number of contracts per each invoice

MySQL Compare Result in WHERE clause

I imagine I'm missing something pretty obvious here.
I'm trying to display a list of 'bookings' where the total charges is higher than the total payments for the booking. The charges and payments are stored in separate tables linked using foreign keys.
My query so far is:
SELECT `booking`.`id`,
SUM(`booking_charge`.`amount`) AS `charges`,
SUM(`booking_payment`.`amount`) AS `payments`
FROM `booking`
LEFT JOIN `booking_charge` ON `booking`.`id` = `booking_charge`.`booking_id`
LEFT JOIN `booking_payment` ON `booking`.`id` = `booking_payment`.`booking_id`
WHERE `charges` > `payments` ///this is the incorrect part
GROUP BY `booking`.`id`
My tables look something like this:
Booking (ID)
Booking_Charge (Booking_ID, Amount)
Booking_Payment (Booking_ID, Amount)
MySQL doesn't seem to like comparing the results from these two tables, I'm not sure what I'm missing but I'm sure it's something which would be possible.
try HAVING instead of WHERE like this
SELECT `booking`.`id`,
SUM(`booking_charge`.`amount`) AS `charges`,
SUM(`booking_payment`.`amount`) AS `payments`
FROM `booking`
LEFT JOIN `booking_charge` ON `booking`.`id` = `booking_charge`.`booking_id`
LEFT JOIN `booking_payment` ON `booking`.`id` = `booking_payment`.`booking_id`
GROUP BY `booking`.`id`
HAVING `charges` > `payments`
One of the problems with the query is the cross join between rows from `_charge` and rows from `_payment`. It's a semi-Cartesian join. Each row returned from `_charge` will be matched with each row returned from `_payment`, for a given `booking_id`.
Consider a simple example:
Let's put a single row in `_charge` for $40 for a particular `booking_id`.
And put two rows into `_payment` for $20 each, for the same `booking_id`.
The query will would return total charges of $80. (= 2 x $40). If there were instead five rows in \'_payment\' for $10 each, the query would return a total charges of $200 ( = 5 x $40)
There's a couple of approaches to addressing that issue. One approach is to do the aggregation in an inline view, and return the total of the charges and payments as a single row for each booking_id, and then join those to the booking table. With at most one row per booking_id, the cross join doesn't give rise to the problem of "duplicating" rows from _charge and/or _payment.
For example:
SELECT b.id
, IFNULL(c.amt,0) AS charges
, IFNULL(p.amt,0) AS payments
FROM booking b
LEFT
JOIN ( SELECT bc.booking_id
, SUM(bc.amount) AS amt
FROM booking_charge bc
GROUP BY bc.booking_id
) c
ON c.booking_id = b.id
LEFT
JOIN ( SELECT bp.booking_id
, SUM(bp.amount) AS amt
FROM booking_payment bp
GROUP BY bp.booking_id
) p
ON p.booking_id = b.id
WHERE IFNULL(c.amt,0) > IFNULL(p.amt,0)
We could make use of a HAVING clause, in place of the WHERE.
The query in this answer is not the only way to get the result, nor is it the most efficient. There are other query patterns that will return an equivalent result.

Selecting a summed value from a subquery that relies on a joined table

So I'm the lucky guy who gets to optimize a query for our application that's taking far too long for the data we're getting. The data we're looking for isn't significantly complex, but the crappy database design is making it a lot harder then it should be (which is great, because I'm the one who designed it about a year ago).
The general idea is we're trying to calculate the total sales (they buy something that increases their balance) and the total payments (they paid money against their balance) for each customer.
The tables:
Customers
id
company
Sales (invoices):
id
customer_id
Payments (account_payments)
id
customer_id
transaction_id (links to invoice_transactions)
Transactions (invoice_transactions)
id
invoice_id (links to invoices, null if payment)
amount
If a user makes a sale, the info is recorded in invoices and invoice_transactions, with invoice_transactions having the invoice_id of the invoices record that contains the customer_id.
If the user makes a payment, the info is recording in account_payments and invoice_transactions, with invoice_transaction having an invoice_id of NULL, and account_payments containing the transaction_id as well as the customer_id.
I know, it's horrible... And I thought I was being clever! Well, I thought the problem through, and came up with a decent solution:
SELECT SQL_NO_CACHE
c.company,
(SELECT SUM(amount) FROM sales),
(SELECT SUM(amount) FROM payments)
FROM customers c
JOIN invoices i ON i.customer_id = c.id
JOIN invoice_transactions sales ON i.invoice_id = sales.id
JOIN account_payments ap ON ap.customer_id = c.id
JOIN invoice_transactions payments ON ap.transaction_id = payments.id
Which does absolutely nothing except give me an error "#1146 - Table 'db.sales' doesn't exist". I'm guessing it has something to do with sub queries being read prior to joins, but I honestly have no idea. And unfortunately I have no idea of another way to approach this problem... Much appreciated if anyone could give me a hand!
I think the best approach would be to separate the the elements Sales and Payments into subqueries, your current method is cross joining all payments with all invoices before doing the aggregation.
SELECT c.ID,
c.Company,
COALESCE(Sales.Amount, 0) AS Sales,
COALESCE(Payments.Amount, 0) AS Payments
FROM Customers c
LEFT JOIN
( SELECT Customer_ID, SUM(Amount) AS Amount
FROM Invoices
INNER JOIN invoice_transactions
ON Invoice_ID = Invoices.ID
GROUP BY Customer_ID
) As Sales
ON Sales.Customer_ID = c.ID
LEFT JOIN
( SELECT Customer_ID, SUM(Amount) AS Amount
FROM Account_Payments
INNER JOIN invoice_transactions tr
ON tr.ID = Transaction_ID
GROUP BY Customer_ID
) AS Payments
ON Payments.Customer_ID = c.ID;
This will include customers with no invoices and no payments. You can change the left joins to inner joins to manipulate this.
Working Example on SQL Fiddle
Your query doesn't make sense.
After doing all the joining, why not just use the tables in the "from" clause:
SELECT c.company, SUM(sales.amount), SUM(payments.amount)
FROM customers c JOIN invoices i ON i.customer_id = c.id JOIN
invoice_transactions sales ON i.invoice_id = sales.id JOIN
account_payments ap ON ap.customer_id = c.id JOIN
invoice_transactions payments ON ap.transaction_id = payments.id
group by c.company
Just giving a table an alias in the "from" clause does not make it available in subqueries elsewhere in the query.
I also added a GROUP BY clause, since your query seems to be aggregating by company.