Mysql get sum of two tables columns grouped - mysql

I have 3 tables:
I would like to select the difference of the total gain and total spent per user. So my hypothetical table could be:
I tried this:
SELECT g.total - s.total AS quantity, id FROM
(SELECT SUM(quantity) AS total FROM gain GROUP BY user) AS g,
(SELECT SUM(quantity) AS total FROM spent GROUP BY user) AS s, users
But it doesn't work...

You need to use the users table as base table, to be able to consider all the users, and then LEFT JOIN to the sub queries computing the total spent and total gain. This is because some user may not have any entry in either gain or spent table(s). Also, Coalesce() function handles the NULL (in case of no matching row)
SELECT
u.id AS user,
COALESCE(tot_gain, 0) - COALESCE(tot_spent, 0) AS balance
FROM users AS u
LEFT JOIN (SELECT user, SUM(quantity) as tot_spent
FROM spent
GROUP BY user) AS s ON s.user = u.id
LEFT JOIN (SELECT user, SUM(quantity) as tot_gain
FROM gain
GROUP BY user) AS g ON g.user = u.id

Madhur's solution is fine. An alternative is union all and group by:
select user, sum(gain) as gain, sum(spent) as spent
from ((select user, quantity as gain, 0 as spent
from gain
) union all
(select user, 0, quantity as spent
from spent
)
) u
group by user;
You can join to user if you want users that are not in either table or you need additional columns. However, that join may not be necessary.

Related

MySQL: get average of value from query

I have 3 table.
Users --> UID, Name, Lat, Longg, Pic
Profile --> pid, uid,City, State, About
Feedback--> fid, uid, ratingby, txnid, rating, feedback_type
Feedback_type can be 1-4 from same txnid. So, if user is giving feedback for all the questions then there will be 4 records for same.
Now i need to show the user details along with the average feedback.
Below is the query i have written so far.
SELECT
a.name,
a.uid,
b.city,
a.pic,
b.state,
b.about
FROM
users AS a
INNER JOIN profile AS b
ON
a.uid = b.uid
I am not sure how can i get the average value from feedback table.
I need show user average feedback and to be more specific. Average of all 4 feedback separately.
Also advise if my approach is good or is there any other best practice that i need to follow.
Edit
I can fetch the single record from feedback.
SELECT uid, avg(rating) FROM `feedback` WHERE uid= 8
But not sure how can i get the average for different feedback_type.
You probably need to create a sub-table to find the average ratings of each type from the Feedbackback table (you can categorize feedback_type for calculating average by using group by f.uid, f.feedback_type). After that, you just need to join the resulting query table with the Users and Profile table to get additional data such as Name, City, etc.
SELECT u1.Name, ar.uid, ar.average_rating, ar.feedback_type, p.City, u1.Pic, p.State, p.About
FROM (
SELECT f.uid, f.feedback_type, AVG(f.rating) AS average_rating
FROM Feedback AS f
WHERE f.uid=8
GROUP BY f.uid, f.feedback_type
) AS ar
INNER JOIN Users AS u1 ON ar.uid=u1.UID
INNER JOIN Profile AS p ON ar.uid=p.uid;
Update: If alias is not working, an alternative approach would be to create a temporary table to calculate user's average rating and use the table to join with Users and Profile tables like above
CREATE TEMPORARY TABLE ar
SELECT uid, feedback_type, AVG(rating) AS average_rating
FROM Feedback
WHERE uid=8
GROUP BY uid, feedback_type;
SELECT Users.Name, ar.uid, ar.average_rating, ar.feedback_type, Profile.City, Users.Pic, Profile.State, Profile.About
FROM ar
INNER JOIN Users ON ar.uid=Users.UID
INNER JOIN Profile ON ar.uid=Profile.uid;
Update: If you need to put the records of 4 feedback types in different columns, you only need to group by uid in ar table and use CASE in AVG to filter out the feedback_type to calculate the average in each column
CREATE TEMPORARY TABLE ar
SELECT
uid,
AVG(CASE WHEN feedback_type = 1 THEN rating END) AS average_rating_1,
AVG(CASE WHEN feedback_type = 2 THEN rating END) AS average_rating_2,
AVG(CASE WHEN feedback_type = 3 THEN rating END) AS average_rating_3,
AVG(CASE WHEN feedback_type = 4 THEN rating END) AS average_rating_4
FROM Feedback
WHERE uid=8
GROUP BY uid;
SELECT
Users.Name,
ar.uid,
ar.average_rating_1,
ar.average_rating_2,
ar.average_rating_3,
ar.average_rating_4,
ar.feedback_type,
Profile.City,
Users.Pic,
Profile.State,
Profile.About
FROM ar
INNER JOIN Users ON ar.uid=Users.UID
INNER JOIN Profile ON ar.uid=Profile.uid;
You can get the avg value using this query SELECT AVG(column_name) FROM table_name WHERE condition;.
To get more columns you should try
SELECT AVG(column_name_1) AS a, AVG(column_name_2) AS b, AVG() AS c, AVG() AS d FROM table_name WHERE condition
I hope i was helpful.

SQL Query for getting maximum value from a column Joining from Another Table

This is a slight variant of the question I asked here
SQL Query for getting maximum value from a column
I have a Person Table and an Activity Table with the following data
-- PERSON-----
------ACTIVITY------------
I have got this data in the database about users spending time on a particular activity.
I intend to get the data when every user has spent the maximum number of hours.
My Query is
Select p.Id as 'PersonId',
p.Name as 'Name',
act.HoursSpent as 'Hours Spent',
act.Date as 'Date'
From Person p
Left JOIN (Select MAX(HoursSpent), Date from Activity
Group By HoursSpent, Date) act
on act.personId = p.Id
but it is giving me all the rows for Person and not with the Maximum Numbers of Hours Spent.
This should be my result.
You have several issues with your query:
The subquery to get hours is aggregated by date, not person.
You don't have a way to bring in other columns from activity.
You can take this approach -- joins and group by, but it requires two joins:
select p.*, a.* -- the columns you want
from Person p left join
activity a
on a.personId = p.id left join
(select personid, max(HoursSpent) as max_hoursspent
from activity a
group by personid
) ma
on ma.personId = a.personId and
ma.max_hoursspent = a.hoursspent;
Note that this can return duplicates for a given person -- if there are ties for the maximum.
This is written more colloquially using row_number():
select p.*, a.* -- the columns you want
from Person p left join
(select a.*,
row_number() over (partition by a.personid order by a.hoursspent desc) as seqnum
from activity a
) a
on a.personId = p.id and a.seqnum = 1
ma.max_hoursspent = a.hoursspent;

Using a function in a where-clause

I have a MySQL-Database where I run certain Stored Procedures.
In one of them, I want to sum up the amount a certain user has to pay, the amount he already payed and return all the users, where the amount to be payed isn't equal the amount paid. I came up with the following (simplyfied) query:
SELECT userId, SUM(costs) as sum_costs,
(SELECT SUM(payed)
FROM payments p WHERE ta.userId=p.userId) as sum_payed
FROM ta
GROUP BY ta.userId
ORDER BY ta.userId;
This gives me the sum of the costs and the payment for each user. But I do not know, how I can select only the users, where costs and payment are not equal.
WHERE sum_costs != sum_payed
doesn't work, because mysql doesn't know the column 'sum_costs' in the WHERE-clause.
Any idea, how to get the selection to work?
SELECT * FROM
(
SELECT userId, SUM(costs) as sum_costs,
(SELECT SUM(payed)
FROM payments p WHERE ta.userId=p.userId) as sum_payed
FROM ta
GROUP BY ta.userId
)a
WHERE a.sum_costs <> a.sum_payed
--ORDER BY a.userId;
Update: actually no need for ORDER BY UserId since it will be ordered implicitly by GROUP BY
SELECT * from
(select userId, SUM(costs) as sum_costs
FROM ta
GROUP BY ta.userId) t1
left join
(SELECT userId,SUM(payed) as sum_payed FROM payments p group by userId) t2
on t1.UserId=t2.UserId
where t1.sum_costs<>t2.sum_payed
ORDER BY t1.userId;
WHERE SUM(costs) <> (SELECT SUM(payed) FROM payments p WHERE ta.userId=p.userId)

Optimize Query for MySQL

I want to run a query that generates a revenue report for campaigns. There are 2 tables
members and payments.
members (id, campaign_code)
payments (id, member_id, amount)
I want to create a table that groups by campaign_code, ie in this format
campaign_code, member_count, total_revenue
I am putting all campaigns into an array and running this,
SELECT sum( amount ) AS amt
FROM (members
INNER JOIN payments
ON payments.member_id = members.id
)
WHERE campaign_code = 'XX'
and it is taking a LOT of time. Anyway to optimize this or do it in a single query?
As you said that you need aggregation for all campaign code, try this
SELECT m.campaign_code , count(p.member_id) AS member_count,
SUM( amount ) AS total_revenue
FROM members m, payments p
WHERE p.member_id = m.id
GROUP BY campaign_code;
Make sure to read on mysql group by function
payments (id, member_id, amount)
I want to create a table that groups by campaign_code, ie in this format
campaign_code, member_count, total_revenue
I am putting all campaigns into an array and running this,
select
m.Campaign_Code,
count( distinct p.member_id) as Members,
count(*) as PaymentEntries,
sum( p.amount ) as TotalRevenue
from
members m
join Payments p
on m.id = p.member_id
where
m.campaign_code = 'XX'
If you want all campaigns, just remove the WHERE clause. You mentioned in comments that the tables DO have indexes, but I would ensure that members table has index on campaign_code, and payments has index on member_id.
This query will give a count of distinct members who've contributed, total number of contributions (in case one or more members contributed multiple times), and the totals of all the contributions.
use
where campaing_code in ('XX','YY','ZZ','AA','BB')
and have an index on campaing_code

How to have a query with columns needing different conditions in MySQL?

For a game, I want to count the number of user sign-ups by hour (using MySQL.).
Quite easy, something like that:
SELECT COUNT(*), DAY(date_user), HOUR(date_user)
FROM users,
GROUP BY DAY(date_user), HOUR(date_user)
After that, I want to only take in consideration users which have played the game at least one time. I have a second table with scores.
SELECT COUNT(*), DAY(date_user), HOUR(date_user)
FROM users, scores
WHERE users.id = scores.userid
GROUP BY DAY(date_user), HOUR(date_user)
Great...
Now, I want the two queries to result in one table, like:
Global signups | Signups of playing users | Day | Hour
I have not found a working query for this yet. Should I use unions? Or joins?
Here's one way:
select
day(date_user)
, hour(date_user)
, count(distinct u.id) as GlobalSignups
, count(distinct s.userid) as SignupsOfPlayingUsers
from users u
left join scores s on u.id = s.userid
group by day(date_user), hour(date_user)
Counting distinct user id's gives the total number of users. When the left join fails, s.userid will be NULL, and NULLs are not counted by count(). So count(distinct s.userid) returns the number of users with signups.