mysql - How to fix this query? - mysql

I am really having a headache since the other day on how to fix this mysql statement to get my desired result. I also want to inform that I am new to mysql and prorgramming.
I have 4 tables CUSTOMER, CUSTOMER_ACCT_SETTING, DEBT, and PAYMENT.
Here are the 4 tables with their record so you can relate.
CUSTOMER
CUSTOMER_ACCT_SETTING
DEBT
PAYMENT
When I run this mysql statement:
SELECT C.CUSTOMER_ID, C.NAME, C.ADDRESS, C.CONTACT_NUMBER,
SUM(((CAS.INTEREST_RATE / 100) * D.AMOUNT) + D.AMOUNT) - COALESCE(SUM(P.AMOUNT), 0) AS CURRENT_BALANCE
FROM CUSTOMER C
INNER JOIN CUSTOMER_ACCT_SETTING CAS ON (C.CUSTOMER_ID = CAS.CUSTOMER_ID)
LEFT JOIN DEBT D ON (C.CUSTOMER_ID = D.CUSTOMER_ID)
LEFT JOIN PAYMENT P ON C.CUSTOMER_ID = P.CUSTOMER_ID
GROUP BY (C.CUSTOMER_ID)
ORDER BY C.NAME
The result is below:
PS: The result is ordered by name.
My question is:
1.) Why did I get a negative result on the CURRENT_BALANCE column in the first row? I am expecting the result to be around 16374.528.
My desired result is like this:

You are projecting your payments through all your debts by doing a join with both tables at the same time. So you essentially get 5 applications of your payment on customer 4 and zero applications on all the other customers. (so NULL on P.AMOUNT yields X - NULL = NULL). To see this, remove the "GROUP BY" and the "SUM" and just return your amounts paid and debited. Then if you group/sum these results manually by customer, you'll see what's going on.
To get the results you expect, you will need to use subqueries or some other mechanism like temporary tables. Something like this:
SELECT C.CUSTOMER_ID,
(SELECT SUM(P.AMOUNT) FROM PAYMENT P
WHERE P.CUSTOMER_ID = C.CUSTOMER_ID) AS TOTAL_PAID_BY_CUSTOMER
FROM CUSTOMER C

The answer to #1 is that each row in your result set has the payment attached to it. That is, for customer #1, you're getting three instances of the 8132.

Related

Adding column values together to form 1 result column

I have an online sales system I am developing and I’m working on the billing payment system.
I have the order total $ amount recorded on the database table with the order itself.
Example:
SELECT total FROM Orders WHERE id = '1'
Then, I’ve got another table that includes an individual record for each financial transaction (check, cc, etc.)
Example:
SELECT payment_amount FROM Payments WHERE order_id = '1'
What I would like to do is combine these two together when doing some reporting of which orders have not been paid in full and retrieve the balance of each order. I’d like to do this with a single query if possible...
This was what I tried...
SELECT o.id as order_id, o.total, (SELECT p.payment_amount FROM Payments as p WHERE o.order_id = o.id) as amount_paid_plus FROM Orders as o
This works great if there is only 1 entry in payments...but if there are two, I get this error
Subquery returns more than 1 row
I want the payments table results to be added together into one lump sum amount and returned as one variable in the query
I also need the query to still return info even if there are no payments in the payments table. It will just display amount paid as 0.
You can just change p.payment_amount to SUM(p.payment_amount) in your subquery. Note that you have an error in the subquery, it should probably be
(SELECT SUM(p.payment_amount) FROM Payments as p WHERE p.order_id = o.id)
Note change from o.order_id to p.order_id.
Try this query :
SELECT o.id, o.total, SUM(p.payment_amount) FROM Orders o, Payments p where p.order_id = o.id

How to make my WHERE clause not run a syntax error in SQL?

The questions asks,
"Write a query to display the customer name and the number of payments they have made where the amount on the check is greater than their average payment amount. Order the results by the descending number of payments."
So far I have,
SELECT customerName,
(SELECT COUNT(checkNumber) FROM Payments WHERE
Customers.customerNumber = Payments.customerNumber) AS
NumberOfPayments
FROM Customers
WHERE amount > SELECT AVG(amount)
ORDER BY NumberOfPayments DESC;
But I am getting a syntax error every-time I run out. What am I doing incorrectly in this situation?
The syntax error comes from the fact that you are having an incorrect second subquery: amount > SELECT AVG(amount) doesn't work.
You could use amount > (SELECT AVG(amount) FROM Payments).
That is: complete the subquery and put it between ( ).
However this won't do what you want (plus it is inefficient).
Now since this is not a forum to do your homework for you, I will leave it at this and thus only help you with the actual question: why do you get the syntax error. Keep on looking, you will find it. No better way to learn than to search and find yourself.
I would phrase this as an inner join between the two tables, with a correlated subquery to find the average payment amount per customer:
SELECT
c.customerName,
COUNT(CASE WHEN p.amount > (SELECT AVG(p2.amount) FROM Payments p2
WHERE p2.customerName = c.customerName)
THEN 1 END) AS NumberOfPayments
FROM Customers c
INNER JOIN Payments p
ON c.customerNumber = p.customerNumber
GROUP BY
c.customerNumber
ORDER BY
NumberOfPayments DESC;
Your current query is on the right track, but you need to do something called conditional aggregation to obtain the count. In this case, we aggregate by customer then assert that a given payment amount is greater than his average before we include it in the count.
I would approach this just using JOINs:
SELECT c.customerName,
SUM( p.amount > p2.avg_amount ) as Num_Payments_Larger_Than_Average
FROM Customers c LEFT JOIN
Payments p
ON c.customerNumber = p.customerNumber LEFT JOIN
(SELECT p2.customerNumber, AVG(amount) as avg_amount
FROM payments p2
GROUP BY p2.customerNumber
) p2
ON p2.customerNumber = p.customerNumber
GROUP BY c.customerNumber, c.customerName
ORDER BY Num_Payments_Larger_Than_Average;
Some notes about this answer. First, it uses LEFT JOIN and conditional aggregation. This allows the query to return customers with zero payments larger than their average -- that is, customers with no payments or all of whose payments are the same.
Second, it includes customerNumber in the GROUP BY. I think this is important, because it may be possible for two customers to have the same name.

Can i use the row result of a query to run a sub query and get the data returned?

to be clear I want to avoid for loop in my node.js program
my current approach is a group_concat() query [which is working correctly]
SELECT DISTINCT(c.main), GROUP_CONCAT(c.cId) AS cId_List FROM customers c LEFT JOIN boxes b ON b.boxId = c.boxId WHERE c.opId = ? GROUP BY c.conNo ORDER BY c.conNo ASC;
//response to json
{
"main": 2,
"cId_List": "512,513"
},{
"main": 3,
"cId_List": "514,515,516,517"
},....
The next query i need to run is for every "cId_List"
for(every cId_List){
qry = "SELECT SUM(amount) FROM payments p WHERE p.cId IN (cId_List);"
}
how can I avoid it?
Reasons to avoid it is because there is no limit to no.queries. It Can be 10000+ at a single request.
Added Info
What is happening?
There is are two tables namely customers, payments
There can be multiple rows in customer table with same "connection number [main]"
by doing group concat I am getting the ids of those rows into cId_List
now for every cId_List I want to run the SUM() Query in payments Table
so my result shall be
{
"main": 2,
"cId_List": "512,513", //multiple rows of customers table
"amount_sum": 500 //data from payments table using above cId_List
},{
"main": 3,
"cId_List": "514,515,516,517",
"amount_sum": -200
},....
sqlFiddle
as asked: sqlfiddle explanation
customers.conNo is a unifying column for multiple customers (basically of a family, they are billed together)
customers.cId is the primary key and the separator factor (when we need to bill per person basis)
payments.cId is foreign key of customers.cId and payments are entered as per cId
report needs to be generated according to conNo
so to get all the payments of a conNo I need to send all the appropriate cId to payments table.
I hope this will clear the doubts.
EDIT:
I am checking this query which may be the answer, I would like to know if this query format is good performance wise?
SELECT GROUP_CONCAT(DISTINCT(customers.cId)) AS cId_List, customers.*, payments.cId, SUM(amount) AS amt FROM `payments` left join customers on customers.cId = payments.cId GROUP BY `customers`.`conNo` ORDER BY `customers`.`conNo` ASC
So it seems that you can simply replace all of your code with the following:
SELECT c.conno
, SUM(p.amount) total
FROM customers c
LEFT
JOIN payments p
ON p.cid = c.cid
GROUP
BY c.conno
http://sqlfiddle.com/#!9/a65cf6/11
SELECT SUM(p.amount)
FROM customers AS c
LEFT JOIN payments AS p ON p.cid = c.cid
GROUP BY c.cid
This query seems to work. Can any one tell me if it is appropriate performance wise. Also would like your suggestions if any Thanks to #Strawberry and #Luca Giardina
SELECT
GROUP_CONCAT(DISTINCT(customers.cId)) AS cId_List,
customers.*, payments.cId,
SUM(amount) AS amt
FROM `payments` LEFT JOIN customers ON customers.cId = payments.cId
GROUP BY `customers`.`conNo`
ORDER BY `customers`.`conNo` ASC

mysql inner join giving bad results (?)

The following sql call works fine, returns the correct total retail for customers:
SELECT customer.id,
customer.first_name,
customer.last_name,
SUM(sales_line_item_detail.retail) AS total_retail
FROM sales_line_item_detail
INNER JOIN sales_header
ON sales_header.id = sales_line_item_detail.sales_header_id
INNER JOIN customer
ON customer.id = sales_header.customer_id
GROUP BY sales_header.customer_Id
ORDER BY total_Retail DESC
LIMIT 10
However, i need it to return the customers telephone and email addresses as well.. please keep in mind that not all customers have an email address and telephone number. whenever i left join the email and numbers tables, it throws the total_retail amount off by thousands and I am not sure why.
The following query gives completely wrong results for the total_retail field:
SELECT customer.id,
customer.first_name,
customer.last_name,
IF(
ISNULL( gemstore.customer_phone_numbers.Number),
'No Number..',
gemstore.customer_phone_numbers.Number
) AS Number,
IF(
ISNULL(gemstore.customer_emails.Email),
'No Email...',
gemstore.customer_emails.Email
) AS Email,
SUM(sales_line_item_detail.retail) AS total_retail,
FROM sales_line_item_detail
INNER JOIN sales_header
ON sales_header.id = sales_line_item_detail.sales_header_id
INNER JOIN customer
ON customer.id = sales_header.customer_id
LEFT JOIN gemstore.customer_emails
ON gemstore.customer_emails.Customer_ID = gemstore.customer.ID
LEFT JOIN gemstore.customer_phone_numbers
ON gemstore.customer_phone_numbers.Customer_ID = gemstore.customer.ID
GROUP BY sales_header.customer_Id
ORDER BY total_Retail DESC
LIMIT 10
Any help figuring out why it is throwing off my results is greatly appreciated.
Thanks!
Is it possible that there are multiple records for a Customer_ID in either the customer_emails or customer_phone_numbers tables?
You'll be matching too many records. Try the query without the group by clause and you'll see which ones and how. Most likely the left join's will duplicate order rows on every customer email/phone match.
I am not totally sure, as i can't test this, but the following might be happening.
If there are more than one email or phone number per customer the final result might get multiplied, because of the new joins.
Imagine the query without the group_by and join to sales:
CustomerId Email phoneNumber
1 test#gmx.com 0122233
1 mail#yahoo.com 0122233
The user in this example has 2 mailadresses.
If you would now add the join to sales and the group by, you would have doubled total_retail.
If this should be the case, replacing the LEFT JOIN with an LEFT OUTER JOIN should do the trick. In that case you will however only see the first email/phonenumer of the customer.

Rewrite IN subquery as JOIN

I've never had good performance with IN in MySQL and I've hit a performance issue with it again.
I'm trying to create a view. The relevant part of it is:
SELECT
c.customer_id,
....
IF (c.customer_id IN (
SELECT cn.customer_id FROM customer_notes cn
), 1, 0) AS has_notes
FROM customers c;
Basically, I just want to know if the customer has a note attached to it or not. It doesn't matter how many notes. How can I rewrite this using JOIN to speed it up?
The customers table currently has 1.5 million rows so performance is an issue.
Don't you need the customer ID selected? As it stands, aren't you running the subquery once per customer, and getting a stream of true or false values with no idea which one applies to which customer?
If that is what you need, you don't need to reference the customers table (unless you keep your database in a state of semantic disintegrity and there could be entries in customer_notes for which there is no corresponding customer - but then you have bigger problems than the performance of this query); you can simply use:
SELECT DISTINCT Customer_ID
FROM Customer_Notes
ORDER BY Customer_ID;
to obtain the list of customer ID values with at least one entry in the Customer_Notes table.
If you want a list of Customer ID values and an associated true/false value, then you need to do a join:
SELECT C.Customer_ID,
CASE WHEN N.Have_Notes IS NULL THEN 0 ELSE 1 END AS Has_Notes
FROM Customers AS C
LEFT JOIN (SELECT Customer_ID, COUNT(*) AS Have_Notes
FROM Customer_Notes
GROUP BY Customer_ID) AS N
ON C.Customer_ID = N.Customer_ID
ORDER BY C.Customer_ID;
If this gives poor performance, check that you have an index on Customer_Notes.Customer_ID. If that isn't the issue, study the query plan.
Can't do ... in a view
The petty restrictions on what is allowed in a view is always a nuisance in any DBMS (MySQL is not alone in having restrictions). However, we can do it with a single regular join. I just remembered. COUNT(column) only counts non-null values, returning 0 if all values are null, so - if you don't mind getting a count rather than just 0 or 1 - you can use:
SELECT C.Customer_ID,
COUNT(N.Customer_ID) AS Num_Notes
FROM Customers AS C
LEFT JOIN Customer_Notes AS N
ON C.Customer_ID = N.Customer_ID
GROUP BY C.Customer_ID
ORDER BY C.Customer_ID;
And if you absolutely must have 0 or 1:
SELECT C.Customer_ID,
CASE WHEN COUNT(N.Customer_ID) = 0 THEN 0 ELSE 1 END AS Has_Notes
FROM Customers AS C
LEFT JOIN Customer_Notes AS N
ON C.Customer_ID = N.Customer_ID
GROUP BY C.Customer_ID
ORDER BY C.Customer_ID;
Note that the use of 'N.Customer_ID' is crucial - though any column in the table would do (but you've not divulged the names of any other columns, AFAICR) and I'd normally use something other than the joining column for clarity.
I think EXISTS suits your situation better than JOIN or IN.
SELECT
IF (EXISTS (
SELECT *
FROM customer_notes cn
WHERE c.customer_id = cn.customer_id),
1, 0) AS filter_notes
FROM customers
Try this
SELECT
CASE WHEN cn.customer_id IS NOT NULL THEN 1
ELSE 0
END AS filter_notes
FROM customers c LEFT JOIN customer_notes cn
ON c.customer_id= cn.customer_id