Hi so this is my case I have those tables
Customer {id,name}
Charges {id,amount,customer_id}
Taxes {id,amount,charge_id}
so I want to SUM amount of charges and taxes then group by customer id here is my query
SELECT SUM(ch.amount),SUM(t.amount)
FROM Customer c
LEFT JOIN Charges ch ON ch.customer_id = c.id
LEFT JOIN Taxes t ON t.charge_id = ch.id
GROUP BY c.id;
so in case I have 1 charge for customer than I have 2 taxes for that charge when I use SUM function it's counting amount of charge twice for example in case to show me 10$ it' showing me 20$
I know how can I fix that through subqueries, but I want to know is there any option to get correct value without subqueries like query I use above what can I modify there to fix that.
Thanks !
UPDATED ANSWER WITHOUT SUBQUERIES
SELECT
SUM(CASE WHEN #ch_id != ch.id
THEN ch.amount END) AS ch_amount,
SUM(t.amount) AS t_sum,
c.*,
#ch_id := ch.id
FROM
Customer c
LEFT JOIN charges ch ON c.id = ch.reservation_id
LEFT JOIN taxes t ON ch.id = t.charge_id
GROUP BY rs.id;
You want to know if you can do this without subqueries. No, you can't.
If a row in Charges has more than one corresponding row in Taxes, you can't simply join the tables without duplicating Charges rows. Then, as you have discovered, when you sum them up, you'll get multiple copies.
You need a way to get a virtual table (a subquery) with one row for each Charge.
SELECT ch.customer_id,
ch.amount amount,
tx.tax tax
FROM Charges
LEFT JOIN (
SELECT SUM(amount) tax,
charge_id
FROM Taxes
GROUP BY charge_id
) tx ON ch.id = tx.charge_id
You can then join that subquery to your Customer table to summarize sales by customer.
This is a pain because of the multiple hierarchies. I would suggest:
SELECT c.id, ch.charge_amount, ch.taxes_amount
FROM Customer c LEFT JOIN
(SELECT ch.customer_id, SUM(ch.amount) as charge_amount,
SUM(t.taxes_amount) as taxes_amount
FROM Charges ch LEFT JOIN
(SELECT t.charge_id, SUM(t.amounts) as taxes_amount
FROM taxes t
GROUP BY t.charge_id
) t
ON t.charge_id = ch.id
GROUP BY ch.customer_id
) ch
ON ch.customer_id = c.id;
You are not going to be able to fix this without subqueries of one form or another, if there are multiple charges for a customer or multiple taxes on a charge.
Related
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
I'm trying to create a query to find what is the total amount owed by each customer to the company. It is the GROUP BY customerNumber in the sub query that is creating the problem.
SELECT customerName,
customers.customerNumber,
SUM(quantityOrdered * priceEach) - ( SELECT SUM(amount) AS MoneyPayed FROM payments GROUP BY customerNumber ) AS AmountOwed
FROM payments
INNER JOIN customers ON payments.customerNumber = customers.customerNumber
INNER JOIN orders ON customers.customerNumber = orders.customerNumber
INNER JOIN orderdetails ON orders.orderNumber = orderdetails.orderNumber
GROUP BY customerNumber;
The tables I'm trying to link are payments and orderdetails.
When I get rid of the GROUP BY I get results in negatives as the total SUM of amount is subtracted from each row of SUM(quantityOrdered * priceEach).
How can I change this so that I can return multiple rows from payments to subtract from SUM(quantityOrdered * priceEach) from the order details table.
Link to DB as StackOverflow doesn't allow me to post images
Thanks for help, sorry if format is bad, this is my first post.
You will need a couple of subqueries to meet your requirement. Let us break it down.
First, you need the total value of orders from each customer. You're very close to the correct query for that. It should be
SELECT orders.customerNumber,
SUM(orderdetails.quantityOrdered * orderdetails.priceEach) owed
FROM orders
JOIN orderdetails ON orders.orderNumber = orderdetails.orderNumber
GROUP BY orders.customerNumber
This subquery's result set gives customerNumber and owed, the amount owed. Notice that orders::orderdetails is a one::many relationship, so we're sure we're counting each detail just once, so the SUMs will be correct.
Next we need the amount paid by each customer. This subquery is fairly simple.
SELECT customerNumber,
SUM(amount) paid
FROM payments
GROUP BY customerNumber
Now for the operation you're missing in your question: we need to join these two subqueries to your customers table.
SELECT customers.customerName, customers.customerNumber
owed.owed - paid.paid balance
FROM customers
LEFT JOIN (
SELECT orders.customerNumber,
SUM(orderdetails.quantityOrdered * orderdetails.priceEach) owed
FROM orders
JOIN orderdetails ON orders.orderNumber = orderdetails.orderNumber
GROUP BY orders.customerNumber
) paid ON customers.customerNumber = paid.customerNumber
LEFT JOIN (
SELECT customerNumber,
SUM(amount) paid
FROM payments
GROUP BY customerNumber
) owed ON customers.customerNumber = owed.customerNumber
See how this works? We join a table and two subqueries. Each subquery has either zero or one rows for each row in the table, so we need not use SUMs or GROUP BY in the outer query.
There's only one complication left: what if a customer has never paid anything? Then the value of paid.paid will be NULL after the LEFT JOIN operation. That will force the value of owed - paid to be NULL. So we need more smarts in the SELECT statement to yield correct sums.
SELECT customers.customerName, customers.customerNumber
COALESCE(owed.owed,0) - COALESCE(paid.paid,0) balance
...
COALESCE(a,b) is equivalent to if a is not null then a else b.
Pro tip In queries or subqueries with JOIN operations, always mention table.column instead of just column. The next person to work on your query will thank you.
Link to all my database tables in excel format:
Database
This is the assignment I was tasked with:
Find total deposit, total withdraw, and total difference for each customer.
What I did was
select c.customerid, customername, sum(d.depositamount) as 'Total Deposit', sum(w.withdrawalamount) as 'Total Withdraw', sum(d.depositamount) - sum(w.withdrawalamount) as 'differences'
from customers c left outer join deposits d on c.customerid = d.customerid
left outer join withdrawals w on c.CustomerID = w.CustomerID
group by c.CustomerID
order by c.CustomerID;
The result
My problem is that the "Total Deposit" and "Total Withdraw" have its data doubled. Since these two column data are doubled, the differences is also doubled. I know I can divide by 2 to all the column to solve the problem but I would like to know a proper method in doing this.
My question is how do I join multiple tables in such a way that the data will not be doubled?
(For example, "James Carlton Brokeridge" is suppose to have 450, 380, and 70 repsectively).
You are getting incorrect values because every time a customer makes more than one deposit or withdrawal, that causes the rows in the other (withdrawal/deposit) table to be replicated when the two tables are joined. To work around this, do the sums in subqueries:
select c.customerid,
c.customername,
d.totaldeposit as 'Total Deposit',
w.totalwithdrawal as 'Total Withdraw',
d.totaldeposit - w.totalwithdrawal as 'differences'
from customers c
left outer join (
select customerid, sum(depositamount) as totaldeposit
from deposits
group by customerid
) d on c.customerid = d.customerid
left outer join (
select customerid, sum(withdrawalamount) as totalwithdrawal
from withdrawals
group by customerid
) w on c.customerid = w.customerid
order by c.customerid
I have a customers table, orders table, and payments table.
From these I am trying to get one table that has for each record: customer#, order#, Balance due. But if a customer has no order then I just want to list the customer# with NULL values for the other fields.
Using a LEFT JOIN I was able to do this with just the customer# and order# but I cannot figure out how to get the balance due in there as well. I am fairly new to Mysql and tried to search the answer but was unable.
Here is what works for just the customer# and order#:
SELECT
customers.cust_num,
orders.order_id
FROM customers
LEFT JOIN orders ON customers.cust_num = orders.cust_num
But I am trying to incorporate "orders.invoice_amount - SUM(payments.amount) AS balance_due" as another column where the payments table is related to the orders table by a field called "order_id" in both.
Perhaps something like:
SELECT orders.invoice_amount - SUM(payments.amount) AS balance_due FROM payments, orders WHERE payments.order_id = orders.order_id
Any idea of how I could go about doing that or pointers in the right direction?
do an additional LEFT JOIN, but to a sub-query on payments grouped by order. So, if it finds a record in the prequeried result, you are good to go. Also, I changed to using table "aliases" for shorter readability, especially if table names get long, or you have to join multiple times to the same table in a query.
SELECT
c.cust_num,
coalesce( o.order_id, 0 ) as Order_ID,
coalesce( o.invoice_amount, 0 ) as InvoiceAmount,
coalesce( Prepaid.TotalPaid, 0 ) as TotalPaid,
coalesce( o.invoice_amount - coalesce( PrePaid.TotalPaid, 0 ), 0) as BalanceDue
FROM
customers c
LEFT JOIN orders o
ON c.cust_num = o.cust_num
LEFT JOIN
( select
p.order_id,
sum( p.amount ) as totalPaid
from
payments p
group by
p.order_id ) as PrePaid
on o.order_id = PrePaid.order_id
you can try something like
SELECT
customers.cust_num,
orders.order_id,
orders.invoice_amount - SUM(payments.amount)
FROM customers
LEFT JOIN orders ON customers.cust_num = orders.cust_num
INNER JOIN payments P ON P..order_id = orders.order_id
group by cust_num,order_id,orders.invoice_amount
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.