MySQL - Arithmetic operations with two relations - mysql

How do I get the total amount of money paid by each customer minus the amount collected (em_paid_to)?
table customer
cust_id INT
f_name VARCHAR
l_name VARCHAR
email VARCHAR
c_limit INT
table transaction
id INT
em_paid_by VARCHAR
em_paid_to VARCHAR
amount INT
trans_date DATE
I already tried this to get the total paid by each customer but it did not work:
SELECT C.F_NAME, C.L_NAME, COUNT(T.EM_PAID_BY), SUM(T.AMOUNT)
FROM CUSTOMER C
JOIN TRANSACTION T ON C.EMAIL = T.EM_PAID_BY;
...and this to get the total collected by each customer, still the same error and I need to get the difference between the two results.
SELECT C.F_NAME, C.L_NAME, COUNT(T.EM_PAID_TO), SUM(T.AMOUNT)
FROM CUSTOMER C
JOIN TRANSACTION T ON C.EMAIL = T.EM_PAID_TO;
What I am hoping to get is like this Old McDonald oldmcdonald#gmail.com 2000
i.e (2000 + 4000 + 1000) - (2000 + 3000) = 2000

Your existing queries generate errors because they use aggregate functions (SUM(), COUNT()) without a GROUP BY clause to list all non-aggregated columns.
To solve your requirement, one solution would be to use conditional aggregation:
recover all transactions where the customer's email appears in the em_paid_to or in the em_paid_by clause
group by customer (a sensible option is to add the customer id to the GROUP BY clause, eventhough it is not part of the results)
do conditional counts and sums, depending on whether the records was matched on em_paid_to or em_paid_by
The following query gives you detailed information (count of payments by and to, amounts paid by and to, and balance), you can pick what's relevant for you:
SELECT
c.f_name,
c.l_name,
SUM(c.email = t.em_paid_by) count_paid_by,
SUM(c.email = t.em_paid_to) count_paid_to,
SUM(CASE WHEN c.email = t.em_paid_by THEN t.amound ELSE 0 END) total_paid_by,
SUM(CASE WHEN c.email = t.em_paid_to THEN t.amound ELSE 0 END) total_paid_to,
SUM(CASE WHEN c.email = t.em_paid_by THEN t.amound ELSE -1 * t.amount END) balance
FROM
customer c
INNER JOIN transaction t
ON c.email IN (t.em_paid_by, t.em_paid_to)
GROUP BY
c.cust_id,
c.f_name,
c.l_name
;

I would unpivot the data and aggregate:
select t.email, c.fname, c.lname, sum(t.amount)
from ((select em_paid_by as email, -amount as amount
from transaction t
) union all
(select em_paid_to, amount
from transaction t
)
) t
group by email;
You can join to the customer table to get the additional customer information:
select email, sum(amount)
from cusomer c join
((select em_paid_by as email, -amount as amount
from transaction t
) union all
(select em_paid_to, amount
from transaction t
)
) t
on c.email = t.email
group by t.email, c.fname, c.lname;

I would use correlated subqueries in the SELECT clause:
select c.*,
coalesce((
select sum(amount)
from transaction t
where t.em_paid_by = c.email
), 0)
-
coalesce((
select sum(amount)
from transaction t
where t.em_paid_to = c.email
), 0) as paid_balance
from customer c
If you want more information like the count of transactions, I would use subqueries in the FROM clause:
select c.*,
p.cnt_paid,
r.cnt_received
coalesce(p.sum_paid, 0) as sum_paid,
coalesce(r.sum_received, 0) as sum_received,
coalesce(p.sum_paid, 0) - coalesce(r.sum_received, 0) as paid_balance,
p.cnt_paid + r.cnt_received as total_transactions
from customer c
left join (
select em_paid_by as email, sum(amount) as sum_paid, count(*) as cnt_paid
from transaction
group by em_paid_by
) p on p.email = c.email
left join (
select em_paid_to as email, sum(amount) as sum_received, count(*) as cnt_received
from transaction
group by em_paid_to
) r on r.email = c.email

Related

Select the same pairs of values from two tables

I have 4 MySQL tables in my database:
Users (uid, name)
Shops (shopId, shopName)
Reviews (ReviewId, text, userId, shopId, rate, date)
TransactionHistory (transactionId, userId, shopId, Amount, date)
Users write their comments to shops in Reviews table. And user payment history in some shops is saved in the TransactionHistory table.
I need to select all users' reviews for some period in time if that users made payments in that shop at the same period in time.
Select userId, shopId, rate, text, date from Review where date between "01.01.2019" and "01.02.2019" where shopId in (select distinct(shopId) from Shops)
Select userId, shopId, date from TransactionHistory where date between "01.01.2019" and "01.02.2019"
So I have two result sets and some records have the same pair (userId, shopId) - that is what I want to get: all records from 1 SQL request, which pairs (userId, shopId) is present in 2 SQL query.
If I understood that right, all you need is a join statement like this one:
SELECT * FROM t1 LEFT JOIN (t2, t3, t4)
ON (t2.a = t1.a AND t3.b = t1.b AND t4.c = t1.c)
Here is the resource https://dev.mysql.com/doc/refman/8.0/en/join.html
On your case inside the on statement would be what you want to be equal.
Select userId, shopId, rate, text, date from Review r join TransactionHistory th on (r.userId == th.userId and r.shopId == th.shopId) where r.date between "01.01.2019" and "01.02.2019" where r.shopId in (select distinct(shopId) from Shops)
SELECT u.uid, u.name as userName, u.surname, s.name, rate, review, c.amount FROM `rsamazingapp.rsamazingapp.reviews` as r
INNER JOIN `rsamazingapp.cashbacks` as c ON (r.uid = c.uid and r.shop_id = c.shop_id)
INNER JOIN `rsamazingapp.shops` as s on (r.shop_id = s.shop_id)
INNER JOIN `rsamazingapp.users` as u on (r.uid = u.uid)
where r.shop_id in (select distinct shop_id from `rsamazingapp.shops`)
order by rate desc

MySQL, how can I reference a subquery field in a SELECT query

I am trying to calculate account balances in MySQL using the following query
SELECT accountNumber,
(
SELECT amount
FROM accountDebits
WHERE accountNumber = accounts.accountNumber
) debits,
(
SELECT amount
FROM accountCredits
WHERE accountNumber = accounts.accountNumber
) credits,
credits - debits as balance
FROM accounts
But I get this error:
Error Code: 1054. Unknown column 'credits' in 'field list'
How can I refer to the subquery aliases in the select?
Wrap it with another subquery that does the calculation:
SELECT accountNumber, debits, credits, credits - debits as balance
FROM (
SELECT accountNumber,
(
SELECT amount
FROM accountDebits
WHERE accountNumber = accounts.accountNumber
) debits,
(
SELECT amount
FROM accountCredits
WHERE accountNumber = accounts.accountNumber
) credits
FROM accounts
) x
While this gets your query up and running, you'd be better off with an outer join:
SELECT accountNumber, debits, credits, credits - debits as balance
FROM (
SELECT
a.accountNumber,
coalesce(sum(d.amount), 0) debits,
coalesce(sum(c.amount), 0) credits,
FROM accounts a
LEFT JOIN accountDebits d
ON d.accountNumber = a.accountNumber
LEFT JOIN accountCredits c
ON c.accountNumber = a.accountNumber
) x
Use the table.field convention, should work
SELECT accountNumber,
(
SELECT amount
FROM accountDebits
WHERE accountNumber = accounts.accountNumber
) debits,
(
SELECT amount
FROM accountCredits
WHERE accountNumber = accounts.accountNumber
) credits,
credits.amount - debits.amount as balance
FROM accounts
I think you want to do this:
SELECT a.*,
(COALESCE(credits, 0) - COALESCE(debits, 0)) as balance
FROM (SELECT accountNumber,
(SELECT SUM(ad.amount)
FROM accountDebits ad
WHERE ad.accountNumber = a.accountNumber
) as debits,
(SELECT SUM(ac.amount)
FROM accountCredits ac
WHERE ac.accountNumber = a.accountNumber
) credits
FROM accounts a
) a;
You need the subquery because you cannot reference the derived columns in the same SELECT where they are defined. You need the aggregation functions to combine all debits/credits into a single value for each account.

MySQL query only returns value if rows exist

I have the following query which works perfectly, but only if each select finds a row.
I've attempted to add IFNULL to return 0 if no rows were found but I'm still not getting the correct return.
SELECT IFNULL(paid_value,0)-IFNULL(ordered_value,0)+IFNULL(credit_value,0) AS account_balance
FROM
(
SELECT customer_id, SUM(order_total) AS ordered_value
FROM orders
WHERE customer_id = '1'
GROUP BY customer_id
) AS orders
LEFT JOIN
(
SELECT customer_id, SUM(amount) AS paid_value
FROM transactions
WHERE customer_id = '1'
GROUP BY customer_id
) as payments
ON orders.customer_id = payments.customer_id
LEFT JOIN
(
SELECT customer_id, SUM(amount) AS credit_value
FROM credits
WHERE customer_id = '1'
GROUP BY customer_id
) as credits
ON orders.customer_id = credits.customer_id
This query currently returns empty, it's not returning NULL or 0.
When I run
SELECT customer_id, SUM(order_total) AS ordered_value
FROM orders
WHERE customer_id = '1'
GROUP BY customer_id
It also returns empty, not NULL or 0, unless there's a row. In order for the full query to work, each of the 3 separate queries need to have a row in them.
Any ideas?
It if because none of the columns have a result set, so an empty result set is returned, if you want to always display a row in any case you can try with some tricks like this one for example :
SELECT IFNULL(SUM(payments.paid_value),0)-IFNULL(SUM(orders .ordered_value),0)+IFNULL(SUM(credits.credit_value),0) AS account_balance
FROM
(SELECT 1 AS idx, 0 AS paid_value, 0 AS ordered_value, 0 AS credit_value) a
LEFT JOIN
(
SELECT 1 AS idx, customer_id, SUM(order_total) AS ordered_value
FROM orders
WHERE customer_id = '1'
GROUP BY customer_id
) AS orders
ON a.idx = orders.idx
LEFT JOIN
(
SELECT customer_id, SUM(amount) AS paid_value
FROM transactions
WHERE customer_id = '1'
GROUP BY customer_id
) as payments
ON orders.customer_id = payments.customer_id
LEFT JOIN
(
SELECT customer_id, SUM(amount) AS credit_value
FROM credits
WHERE customer_id = '1'
GROUP BY customer_id
) as credits
ON orders.customer_id = credits.customer_id
GROUP BY a.idx
The example is a proof of concept that can be adapted even to other situations where you need to always returns a row with default values even with no elements in underlying tables .
If you want row event if there is no data available, then I suppose you should either have UNION. Or In this scenario you can include you Customer Table & put each Table on Right Side, make a LEFT JOIN.
I think following select will give you an IDEA.
SELECT O.customer_id, SUM(O.order_total) AS ordered_value
FROM Customers C
left join orders O on C.ID =o.customer_id
WHERE O.customer_id = '1'
GROUP BY O.customer_id

Select the SUM of two different tables

I have an orders table which consists of the following:
id
order_total
delivery_cost
customer_id
I also have a transactions table which has:
id
amount
customer_id
status
What I'm trying to do is,
SELECT SUM(order_total + delivery_cost) FROM orders WHERE customer_id = '1'
then
SELECT SUM(amount) FROM transactions WHERE customer_id = '1' AND transaction_status = 'Paid'
Then with the data, minus the total amount from the order totals.
I've tried different queries using JOINS, but I just can't get my head around it, for example:
SELECT SUM(OrdersTotal - TransactionTotal) as AccountBalance
FROM (
SELECT SUM(`order_total` + `delivery_cost`) FROM `orders` as OrdersTotal
UNION ALL
SELECT SUM(`amount`) FROM `transactions` WHERE `transaction_status` = 'Paid' as TransactionTotal
)
but this didn't work at all. Any help would be greatly appreciated.
The two datasets are effectively autonomous but assuming that it's unlikely to have transactions for a customer without orders, you can acheive your result with a LEFT JOIN rather than a ful outer join - but if you simply join the base tables then you'll likely get values from one table repeated in the interim result set (this is why Joseph B's answer is wrong when a customer has something other than a single row in each table).
SELECT ordered_value-IFNULL(paid_value,0) AS acct_balance
FROM
(
SELECT customer_id, SUM(order_total + delivery_cost) AS ordered_value
FROM orders
WHERE customer_id = '1'
GROUP BY customer_id
) AS orders
LEFT JOIN
(
SELECT customer_id, SUM(amount) AS paid_value
FROM transactions
WHERE customer_id = '1'
AND transaction_status = 'Paid'
FROUP BY customer_id
) as payments
ON orders.customer_id = payments.customer_id
(here the 'GROUP BY' and 'ON' clauses are redundant since your only looking at a single customer - but are required for multiple customers).
Note that calculating a balance based on a sum of transactions is technically correct, it does not scale well - for large systems a better solution (although it breaks the rules of normalization) is to maintain a unified table of transactions and a balance for for the account along with each transaction amount - alternatively use checkpointing.
You can just name the column in your union all query and sum on that:
SELECT SUM(col) as AccountBalance
FROM (SELECT SUM(`order_total` + `delivery_cost`) as col FROM `orders` as OrdersTotal
UNION ALL
SELECT SUM(`amount`) FROM `transactions` WHERE `transaction_status` = 'Paid' as TransactionTotal
) t;
Try this query using a JOIN:
SELECT
SUM(o.order_total + o.delivery_cost) - SUM(t.amount) AS AccountBalance
FROM orders o
INNER JOIN transactions t
ON o.customer_id = t.customer_id AND o.customer_id = '1' AND t.transaction_status = 'Paid';
This should give you the account balance for each customer?
SELECT
ISNULL(o.customer_id, t.customer_id) AS customer_id
OrdersTotal - TransactionTotal AS AccountBalance
FROM (
SELECT
customer_id,
SUM(order_total + delivery_cost) AS OrdersTotal
FROM
orders
GROUP BY
customer_id) o
FULL OUTER JOIN (
SELECT
customer_id,
SUM(amount) AS TransactionTotal
FROM
transactions
WHERE
transaction_status = 'Paid'
GROUP BY
customer_id) t
ON t.customer_id = o.customer_id
You can join the results of your queries and perform your calculations ,note sum without group by will result as one row so the sum in outer query doesn't mean when you have only one row and according to your logic of calculation union has nothing to do with it
SELECT t1.OrdersTotal - t2 .TransactionTotal AS AccountBalance
FROM (
SELECT SUM(order_total + delivery_cost) OrdersTotal ,customer_id
FROM orders
WHERE customer_id = '1'
) t1
JOIN (
SELECT SUM(amount) TransactionTotal ,customer_id
FROM transactions
WHERE customer_id = '1' AND transaction_status = 'Paid'
) t2 USING(customer_id)
Since you are only concerned about a single customer, just have them listed as two different queries as the source...
select
charges.chg - paid.pay as Balance
from
( SELECT SUM(order_total + delivery_cost) chg
FROM orders
WHERE customer_id = '1' ) charges,
( SELECT SUM(amount) pay
FROM transactions
WHERE customer_id = '1' AND transaction_status = 'Paid' ) paid
Now, if you wanted for all customers to see who is outstanding, add the customer's ID to each query and apply a group by, but then change to a LEFT-JOIN so you get all orders with or
select
charges.customer_id,
charges.chg - coalesce( paid.pay, 0 ) as Balance
from
( SELECT customer_id, SUM(order_total + delivery_cost) chg
FROM orders
group by customer_id ) charges
LEFT JOIN ( SELECT customer_id, SUM(amount) pay
FROM transactions
where transaction_status = 'Paid'
group by customer_id ) paid
on charges.customer_id = paid.customer_id
I think this works best if you try an inline view. In the code block below, check the Group By clause, you might need to add more fields in the Group By depending on what you're selecting in the Inner SELECT statements.Try the code below:
SELECT
SUM(Totals.OrdersTotal-Totals.TransactionTotal)
FROM
(SELECT
SUM(ord.order_total + ord.delivery_cost) AS OrdersTotal
, SUM(trans.amount) AS TransactionTotal
FROM orders ord
INNER JOIN transactions trans
ON ord.customer_id = trans.customer_id
WHERE
ord.customer_id =1
AND trans.transaction_status = 'Paid'
GROUP BY
ord.customer_id
) Totals;

MySQL Query not displaying correctly

I am having to set up a query that retrieves the last comment made on a customer, if no one has commented on them for more than 4 weeks. I can make it work using the query below, but for some reason the comment column won't display the latest record. Instead it displays the oldest, however the date shows the newest. It may just be because I'm a noob at SQL, but what exactly am I doing wrong here?
SELECT DISTINCT
customerid, id, customername, user, MAX(date) AS 'maxdate', comment
FROM comments
WHERE customerid IN
(SELECT DISTINCT id FROM customers WHERE pastdue='1' AND hubarea='1')
AND customerid NOT IN
(SELECT DISTINCT customerid FROM comments WHERE DATEDIFF(NOW(), date) <= 27)
GROUP BY customerid
ORDER BY maxdate
The first "WHERE" clause is just ensuring that it shows only customers from a specific area, and that they are "past due enabled". The second makes sure that the customer has not been commented on within the last 27 days. It's grouped by customerid, because that is the number that is associated with each individual customer. When I get the results, everything is right except for the comment column...any ideas?
Join much better to nested query so you use the join instead of nested query
Join increase your speed
this query resolve your problem.
SELECT DISTINCT
customerid,id, customername, user, MAX(date) AS 'maxdate', comment
FROM comments inner join customers on comments.customerid = customers.id
WHERE comments.pastdue='1' AND comments.hubarea='1' AND DATEDIFF(NOW(), comments.date) <= 27
GROUP BY customerid
ORDER BY maxdate
I think this might probably do what you are trying to achieve. If you can execute it and maybe report back if it does or not, i can probably tweak it if needed. Logically, it ' should' work - IF i have understood ur problem correctly :)
SELECT X.customerid, X.maxdate, co.id, c.customername, co.user, co.comment
FROM
(SELECT customerid, MAX(date) AS 'maxdate'
FROM comments cm
INNER JOIN customers cu ON cu.id = cm.customerid
WHERE cu.pastdue='1'
AND cu.hubarea='1'
AND DATEDIFF(NOW(), cm.date) <= 27)
GROUP BY customerid) X
INNER JOIN comments co ON X.customerid = co.customerid and X.maxdate = co.date
INNER JOIN customer c ON X.customerid = c.id
ORDER BY X.maxdate
You need to have subquery for each case.
SELECT a.*
FROM comments a
INNER JOIN
(
SELECT customerID, max(`date`) maxDate
FROM comments
GROUP BY customerID
) b ON a.customerID = b.customerID AND
a.`date` = b.maxDate
INNER JOIN
(
SELECT DISTINCT ID
FROM customers
WHERE pastdue = 1 AND hubarea = 1
) c ON c.ID = a.customerID
LEFT JOIN
(
SELECT DISTINCT customerid
FROM comments
WHERE DATEDIFF(NOW(), date) <= 27
) d ON a.customerID = d.customerID
WHERE d.customerID IS NULL
The first join gets the latest record for each customer.
The second join shows only customers from a specific area, and that they are "past due enabled".
The third join, which uses LEFT JOIN, select all customers that has not been commented on within the last 27 days. In this case,only records without on the list are selected because of the condition d.customerID IS NULL.
But tomake your query shorter, if the customers table has already unique records for customer, then you don't need to have subquery on it.Directly join the table and put the condition on the WHERE clause.
SELECT a.*
FROM comments a
INNER JOIN
(
SELECT customerID, max(`date`) maxDate
FROM comments
GROUP BY customerID
) b ON a.customerID = b.customerID AND
a.`date` = b.maxDate
INNER JOIN customers c
ON c.ID = a.customerID
LEFT JOIN
(
SELECT DISTINCT customerid
FROM comments
WHERE DATEDIFF(NOW(), date) <= 27
) d ON a.customerID = d.customerID
WHERE d.customerID IS NULL AND
c.pastdue = 1 AND
c.hubarea = 1
Two of your table columns are not contained in either an aggregate function or the GROUP BY clause. for example suppose that you have two data rows with the same customer id and same date, but with different comment data. how SQL should aggregate these two rows? :( it will generate an error...
try this
select customerid, id, customername, user,date, comment from(
select customerid, id, customername, user,date, comment,
#rank := IF(#current_customer = id, #rank+ 1, 1),
#current_customer := id
from comments
where customerid IN
(SELECT DISTINCT id FROM customers WHERE pastdue='1' AND hubarea='1')
AND customerid NOT IN
(SELECT DISTINCT customerid FROM comments WHERE DATEDIFF(NOW(), date) <= 27)
order by customerid, maxdate desc
) where rank <= 1