Select the same pairs of values from two tables - mysql

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

Related

MySQL - Arithmetic operations with two relations

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

MySQL subquery fetch only latest value from JOIN table

I have the following MySQL query. The user_extra table can return multiple values for a given user_id. I would like only the latest a.session_date value from user_extra.
SELECT DISTINCT (user_id) user_id, a.value
FROM user_reg ap
JOIN user_extra a USING (user_id)
WHERE ap.session_date BETWEEN '2017-01-01' AND '2017-06-10' AND a.session_date<='2017-01-01'
Any ideas how I can do this?
select ap.user_id, ap1.value
from user_reg ap
inner join (
select user_id, max(session_date) as MaxDate, value
from user_extra
group by user_id
) ap1 on ap.user_id = ap1.user_id and ap.date = ap1.MaxDate
It will give maximum session date for single user id.
One approach is to join to a subquery which finds the latest records. This is probably the best way to go here as MySQL does not have analytic functions available. In this case, we can add one more join to your query to restrict the user_extra table to having the record corresponding to the most recent session for each user.
SELECT
ur.user_id,
ue1.value
FROM user_reg ur
INNER JOIN user_extra ue1
ON ur.user_id = ue1.user_id AND
ue1.session_date <= '2017-01-01'
INNER JOIN
(
SELECT user_id, MAX(session_date) AS max_session_date
FROM user_extra
GROUP BY user_id
) ue2
ON ue1.user_id = ue2.user_id AND
ue1.session_date = ue2.max_session_date
WHERE
ur.session_date BETWEEN '2017-01-01' AND '2017-06-10';
If you would want to get a list of all the values of a user use:
SELECT A.user_id, GROUP_CONCAT(B.value) LIST_OF_VALUES FROM
(SELECT user_id, value FROM user_reg
WHERE session_date BETWEEN '2017-01-01' AND '2017-06-10'
GROUP BY user_id, value) A INNER JOIN
(SELECT user_id, MAX(session_date) FROM user_extra
WHERE session_date<='2017-01-01' GROUP BY user_id) B
ON A.user_id=B.user_id
GROUP BY A.user_id;
But if you would want to get the total values of a user use:
SELECT A.user_id, SUM(B.value) SUM_OF_VALUES FROM
(SELECT user_id, value FROM user_reg
WHERE session_date BETWEEN '2017-01-01' AND '2017-06-10'
GROUP BY user_id, value) A INNER JOIN
(SELECT user_id, MAX(session_date) FROM user_extra
WHERE session_date<='2017-01-01' GROUP BY user_id) B
ON A.user_id=B.user_id
GROUP BY A.user_id;

Query on two tables with belongs_to/has_many relation

One table is Users with id and email columns.
Another table is Payments with id, created_at, user_id and foo columns.
User has many Payments.
I need a query that returns each user's email, his last payment date and this last payment's foo value. How do I do that? What I have now is:
SELECT users.email, MAX(payments.created_at), payments.foo
FROM users
JOIN payments ON payments.user_id = users.id
GROUP BY users.id
This is wrong, because foo value does not necessarily belong to user's most recent payment.
Try this :
select users.email,foo,create_at
from users
left join(
select a.* from payments a
inner join (
select id,user_id,max(create_at)
from payments
group by id,user_id
)b on a.id = b.id
) payments on users.id = payments.user_id
If users has no payment yet, then foo and create_at would return NULL. if you want to exclude users who has no payment, then use INNER JOIN.
One approach would be to use a MySQL version of rank over partition and then select only those rows with rank = 1:
select tt.email,tt.created_at,tt.foo from (
select t.*,
case when #cur_id = t.id then #r:=#r+1 else #r:=1 end as rank,
#cur_id := t.id
from (
SELECT users.id,users.email, payments.created_at, payments.foo
FROM users
JOIN payments ON payments.user_id = users.id
order by users.id asc,payments.created_at desc
) t
JOIN (select #cur_id:=-1,#r:=0) r
) tt
where tt.rank =1;
This would save hitting the payments table twice. Could be slower though. Depends on your data!

Speeding up mysql inner query

I currently have:
SELECT ads, id as mid, (SELECT IFNULL(SUM(amount),0) FROM trans WHERE paidout=0 AND user_id=mid) as amount FROM accounts
How could I execute the inner sum query and then go through it and put together the balances in the same manner?
The subquery will be execute for each row returned. If you convert it into a LEFT JOIN, it will be executed once and should be drastically faster.
SELECT
ads,
id AS mid,
COALESCE(amount, 0) AS amount
FROM
accounts
LEFT JOIN (
/* Subquery returns SUM(amount) per user_id */
/* LEFT JOIN in case there are no matching records */
SELECT user_id, SUM(amount) AS amount
FROM trans
WHERE paidout = 0
GROUP BY user_id
) amt ON accounts.id = amt.user_id
The SUM() can be done in the outer query in your case as well, applying the GROUP BY over all other columns.
SELECT
accounts.ads,
accounts.id AS mid
COALESCE(SUM(amount), 0) AS amount
FROM
accounts
LEFT JOIN trans ON accounts.id = trans.user_id
WHERE trans.paidout = 0
GROUP BY
accounts.ads,
mid
Something like this should work:
SELECT ads, accounts.id as mid, ifnull(sum(amount),0) as amount
FROM accounts
LEFT JOIN trans ON paidout=0 AND user_id=accounts.id
GROUP BY accounts.id
This will only have two selects VS (1+(num of rows in amount)) selects, as you have it today.
select ads, mid, IFNULL(SUM(amount),0) as total
from (
SELECT a.ads, a.id as mid, amount
from accounts a
join trans t on t.user_id = a.id and t.paidout = 0
)
group by ads, mid

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