Calculate average message quality per user in MySQL - mysql

Consider the following tables:
users messages
------------------- ----------------------
user_id avg_quality msg_id user_id quality
------------------- ----------------------
1 1 1 1
2 2 1 0
3 3 1 0
4 1 1
5 1 1
6 2 0
7 2 0
8 3 1
messages.quality is either 0 or 1. I need to calculate the average message quality per user and update users.avg_quality accordingly. So the desired output would be modified users table like so:
users
-------------------
user_id avg_quality <-- DECIMAL (8,2)
-------------------
1 0.60 <-- (3x1 + 2x0) / 5
2 0.00 <-- (2x0) / 2
3 1.00 <-- (1x1) / 1
I've begun my query like this, I know the syntax is incorrect but have no better idea. Do you?
UPDATE messages m, users u
SET avg_quality = (SELECT COUNT(m.msg_id) / SUM(m.quality))
WHERE m.user_id = u.user_id

This should work:
UPDATE users u
INNER JOIN (SELECT a.user_id, AVG(quality) avg_quality
FROM messages a
INNER JOIN users b
ON a.user_id = b.user_id
GROUP BY a.user_id
) tmp
ON u.user_id = tmp.user_id
SET u.avg_quality = tmp.avg_quality;

See the average function:
http://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html#function_avg
Your select should be something like this:
select user_id, AVG(quality) from messages group by user_id
If you begin with an empty users table you could run a query like this one to update it all:
insert into users (user_id, avg_quality)
select m.user_id, coalesce(AVG(m.quality),0) from messages m group by m.user_id
If you need continuous results Luc's proposal will work for you:
update users u left join (
select m.user_id, AVG(m.quality) as average from messages m group by m.user_id
) as average_result_t on u.user_id = average_result_t.user_id
set u.average = coalesce(average_result_t.average,0)

Related

How do I sum amount for specific user in mysql query?

I have three tables I am working with:
users table contains users id, name and status
supply_history table contains id, users_id, amount and status
rejected_deal table contains id, users_id, rejected_amount and also status.
What I am trying to find is to sum up each users supply_amount and rejected amount where users status, supply_history status and rejected_deal status is 1.
Here is what I have tried so far.
SELECT users.id, users.name,
sum(supply_history.amount) as supply_amount,
sum(rejected_deal.rejected_amount) as rejected_amount
from users
inner join supply_history on supply_history.users_id=users.id
inner join rejected_deal on rejected_deal.users_id=users.id
where users.status='1' and rejected_deal.status='1' and supply_history.status='1'
GROUP by users.id;
My current query result is bellow.
id
name
supply_amount
rejected_amount
1
Skipper
50
20
2
Private
200
20
The result I am looking for
id
name
supply_amount
rejected_amount
1
Skipper
50
10
2
Private
100
20
Bellow is my three tables
users table.
id
name
status
1
Skipper
1
2
Private
1
supply_history table.
id
users_id
amount
status
1
1
20
1
2
1
30
1
3
2
100
1
rejected_deal table.
id
users_id
rejected_amount
status
1
2
15
1
2
2
5
1
3
1
10
1
How do I solve this query please help. Thank you.
You are joining supply_history rows and rejected_deal rows to user rows. But supply_history and rejected_deal are not directly related. For one user, you create thus all combinations of supply_history and rejected_deal. The result is:
id
name
status
sh_id
users_id
amount
status
rd_id
users_id
rejected_amount
status
1
Skipper
1
1
1
20
1
2
2
5
1
1
Skipper
1
2
1
30
1
3
1
10
1
2
Private
1
3
2
100
1
1
2
15
1
2
Private
1
3
2
100
1
1
2
15
1
When you aggregate this data set, you add up amounts multifold. (Each value gets multiplied with the number of rows for the user in the other table).
Instead join the sums to the user:
select u.id, u.name, sh.supply_amount, rd.rejected_amount
from users u
left outer join
(
select users_id, sum(amount) as supply_amount
from supply_history
where status = 1
group by users_id
) sh on sh.users_id = u.id
left outer join
(
select users_id, sum(rejected_amount) as rejected_amount
from rejected_deal
where status = 1
group by users_id
) rd on rd.users_id = u.id
where u.status = 1
order by u.id;
I've turned your inner joins into outer joins for the case a user doesn't have entries in both tables. If you only want users that have data in both tables, then change this back to inner joins.
And if you want to enhance the query's readability, you may consider using WITH clauses: https://dev.mysql.com/doc/refman/8.0/en/with.html.
use Over for sum like this
SELECT users.id, users.name,
sum(supply_history.amount) OVER(PARTITION BY users.id, ORDER BY users.id) as supply_amount,
sum(rejected_deal.rejected_amount) OVER(PARTITION BY users.id, ORDER BY users.id) as rejected_amount
from users
inner join supply_history on supply_history.users_id=users.id
inner join rejected_deal on rejected_deal.users_id=users.id
where users.status='1' and rejected_deal.status='1' and supply_history.status='1'
try using sub queries to be something like that :
SELECT users.id, users.name,
(select sum(amount) from supply_history s where s.users_id = users.id and s.status = '1' ) as supply_amount ,
(select sum(rejected_amount) from rejected_deal r where r.users_id = users.id and r.status = '1' ) as rejected_amount
from users
where users.status='1';

MySQL - Display null column from child table if all values are not distinct

I have the following tables, for example:
invoices
ID Name
1 A
2 B
3 C
4 D
5 E
transactions
ID Invoice_ID User_ID
1 1 10
2 1 10
3 1 10
4 2 30
5 3 20
6 3 40
7 2 30
8 2 30
9 4 40
10 3 50
Now I want to make a select that will pull the invoices and the user_id from the related transactions, but of course if I do that I won't get all the ids, since they may be distinct but there will be only one column for that. What I want to do is that if there are distinct User_ids, I will display a pre-defined text in the column instead of the actual result.
select invoices.id, invoices.name, transactions.user_id(if there are distinct user_ids -> return null)
from invoices
left join transactions on invoices.id = transactions.invoice_id
and then this would be the result
ID Name User_ID
1 A 10
2 B 30
3 C null
4 D 40
5 E null
Is this possible?
You can do the following :
select
invoices.id,
invoices.name,
IF (
(SELECT COUNT(DISTINCT user_id) FROM transactions WHERE transactions.invoice_id = invoices.id) = 1,
(SELECT MAX(user_id) FROM transactions WHERE transactions.invoice_id = invoices.id),
null
) AS user_id
from invoices
Or, alternatively, you can use the GROUP_CONCAT function to output a comma-separated list of users for each invoice. It is not exactly what you asked, but maybe in fact it will be more useful :
select
invoices.id,
invoices.name,
GROUP_CONCAT(DISTINCT transactions.user_id SEPARATOR ',') AS user_ids
from invoices
left join transactions on invoices.id = transactions.invoice_id
group by invoices.id
Try somethingh like:
select i.id, i.name, t.user_id
from invoices i left join
(
select invoice_ID, User_ID
from transactions
group by invoice_ID
having count(invoice_ID)=1
) t on i.id=t.invoice_id
SQL fiddle
You could list all the transactions that have multiple user ids, like this:
select invoices.id, invoices.name, null
from invoices
left join transactions on invoices.id = transactions.invoice_id having count(distinct transactions.user_id) > 1
Also, I think this CASE might suit your needs here:
select invoices.id, invoices.name,
case when count(distinct transactions.user_id) > 1 then null else transactions.user_id end
from invoices
left join transactions on invoices.id = transactions.invoice_id
group by invoices.id
although, I'm not sure this is syntactically correct

SQL - How to calculate column value and join with another table

As I am not good with MySQL query's so I wish someone help me for creating this kind of sql query.
I having two MySQL tables which is describe bellow:
Table Name: rating
-------------------
property_id user_id area_rate_count safety_rate_count friendly_rate_count walkability_rate_count
4 28 1 1 1 2
5 38 2 3 4 1
5 40 2 2 3 1
6 40 2 3 1 4
10 43 2 2 3 1
Table Name: listing
-------------------
property_id title
4 Sample 1
5 Sample 2
6 Sample 3
10 Sample 4
11 Sample 5
12 Sample 6
Now first I want to sum each column and divide. (area_rate_count, safety_rate_count, friendly_rate_count, walkability_rate_count). For example In property_id:5 having two times so first calculate column sum and divide by 2.
After calculation we will get this output:
Table Name: rating (After Calculation)
--------------------------------------
property_id rate
4 5
5 9 (Divided by 2 because this property_id is two times in table)
6 10
10 8
And Finally I want join this result to my listing table and result looks something like this:
Table Name: listing
-------------------
property_id title rate
4 Sample 1 5
5 Sample 2 9 (Divided by 2 becouse property_id is two times in table)
6 Sample 3 10
10 Sample 4 8
11 Sample 5 0
12 Sample 6 0
Thanks.
I think you want the avg() aggregation function along with a join:
select l.property_id, l.title,
coalesce(avg(area_rate_count + safety_rate_count + friendly_rate_count + walkability_rate_count
), 0) as rate
from listing l left outer join
property_id p
on l.property_id = p.property_id
group by l.property_id, l.title ;
If I understood it right I think you need this:
select l.property_id, l.title, coalesce(r.ssum/if(r.ct=0,1,r.ct), 0) as rate
from listing l LEFT JOIN
(select property_id,
sum(area_rate_count+safety_rate_count
+friendly_rate_count+walkability_rate_count) ssum,
count(*) ct
from rating
group by property_id ) r
ON l.property_id = r.property_id
order by l.property_id
See it here on fiddle: http://sqlfiddle.com/#!2/589d6/5
Edit
As OP asked on the comments that he wants all columns from listing here is what he want:
select l.*, coalesce(r.ssum/if(r.ct=0,1,r.ct), 0) as rate
from listing l LEFT JOIN
(select property_id,
sum(area_rate_count+safety_rate_count
+friendly_rate_count+walkability_rate_count) ssum,
count(*) ct
from rating
group by property_id ) r
ON l.property_id = r.property_id
order by l.property_id
CREATE TEMPORARY TABLE IF NOT EXISTS
temp_table ( INDEX(col_2) )
ENGINE=MyISAM
AS (
SELECT
property_id,
AVG(area_rate_count) as area_rate_count,
AVG(safety_rate_count) as safety_rate_count,
AVG(friendly_rate_count) as friendly_rate_count,
AVG(walkability_rate_count) as walkability_rate_count
FROM rating
GROUP BY property_id
)
SELECT * FROM listing L
JOIN temp_table T
ON L.property_id = T.property_id
Use the below statement to get distinct property_id with its own rate
select property_id, sum(separaterating)/count(property_id) from (
select property_id,sum(area_rate_count , safety_rate_count , friendly_rate_count , walkability_rate_count) as separaterating from rating group by property_id AS temp ) group by
property_id
you can then join with the other table to get the final result as below
select * from ( select property_id, sum(separaterating)/count(property_id) from (
select property_id,sum(area_rate_count , safety_rate_count , friendly_rate_count , walkability_rate_count) as separaterating from rating group by property_id AS temp ) group by
property_id) AS A inner join listing AS B on A.property_id = B.property_id
try this:
select a.prop_id as property_id, l.title, a.allratings / b.numberofreviews as rate
from
(
select property_id as prop_id, SUM(coalesce(area_rate_count,0) + coalesce(safety_rate_count,0) + coalesce(friendly_rate_count,0) + coalesce(walkability_rate_count,0)) as allratings
from rating
group by property_id
) a inner join
(
select property_id, count(distinct user_id) as numberofreviews
from rating
group by property_id
) b on a.property_id = b.property_id
inner join listing l on a.property_id = l.property_id
Try This Query
select ls.property_id,ls.title,inr.rate from listing as ls
left join
(select r.property_id as pid,r.rate/r.cnt as rate from
(select property_id,user_id,(area_rate_count+safefty_rate_count+friendly_rate_count+walkability_rate_count) as rate,count(*) as cnt from rating group by property_id) as r) as inr on inr.pid=ls.property_id

SQL: group by from other table and invert result

I have some problem with my SQL query
I have table message and table recipient
message is
ID author date
--------------------
0 1 2013-07-08 05:38:47
1 1 2013-07-13 05:38:47
2 1 2013-07-15 05:38:47
3 1 2013-07-15 05:38:47
4 2 2013-07-17 05:38:47
5 1 2013-07-28 05:38:47
recipient is
ID m_id recipient
--------------------
0 0 2
1 1 2
2 2 3
3 3 2
4 4 1
5 5 2
I need return rows from table message with group by recipient column from table recipient with last date in message table
I'll try this
SELECT m.*
FROM message as m
INNER JOIN recipient as r ON (m.ID = r.m_id)
WHERE m.author = 1
GROUP BY r.recipient
ORDER BY m.ID DESC
return is
ID author date
--------------------
2 1 2013-07-15 05:38:47
0 1 2013-07-08 05:38:47
but i need
ID author date
--------------------
5 1 2013-07-28 05:38:47
2 1 2013-07-15 05:38:47
please help
I USE MySQL Server 5.1
I found a solution to my problem
SELECT m.*
FROM (
SELECT * FROM recipient
WHERE 1=1
ORDER BY recipient.ID DESC
) AS r
INNER JOIN message AS m ON (r.m_id = m.ID)
WHERE m.author = 1
GROUP BY r.recipient
just reverse table recipient
Very simple and fast in PostgreSQL with DISTINCT ON - not standard SQL so not available in every RDBMS.
The question doesn't mention it, but deriving from the code examples it is actually looking for the row with the "last date" for each recipient for a given author.
SELECT DISTINCT ON (r.recipient) m.*
FROM message m
JOIN recipient r ON r.m_id = m.id
WHERE m.author = 1
ORDER BY r.recipient, m.date DESC, r.m_id -- to break ties
Details as well as multiple SQL standard alternatives here:
Select first row in each GROUP BY group?
Another solution with basic, standard SQL. Works with every major RDBMS, including MySQL (since that tag has been added):
SELECT m.*
FROM message m
JOIN recipient r ON r.m_id = m.id
WHERE m.author = 1
AND NOT EXISTS (
SELECT 1
FROM message m1
JOIN recipient r1 ON r1.m_id = m1.id
WHERE r1.recipient = r.recipient
AND m1.author = 1
AND m1.date > m.date
)
Only the row with the latest date passes the NOT EXISTS anti-semi-join.
Presuming you're using SQL-Server, you can use a ranking function like ROW_NUMBER:
WITH CTE AS(
SELECT m.*,
RN=ROW_NUMBER()OVER(PARTITION BY r.recipient ORDER BY m.date DESC)
FROM message as m
INNER JOIN recipient as r ON (m.ID = r.m_id)
WHERE m.author = 1
)
SELECT * FROM CTE WHERE RN = 1
Demo

mySQL - GROUP BY but get the most recent row

I've got a budget table:
user_id product_id budget created
-----------------------------------------------------------------
1 1 300 2011-12-01
2 1 400 2011-12-01
1 1 500 2011-12-03
2 2 400 2011-12-04
I've also got a manager_user table, joining a manager with the user
user_id manager_id product_id
------------------------------------
1 5 1
1 9 2
2 5 1
2 5 2
3 5 1
What I'd like to do is grab each of the user that's assigned to Manager #5, and also get their 'budgets'... but only the most recent one.
Right now my statement looks like this:
SELECT * FROM manager_user mu
LEFT JOIN budget b
ON b.user_id = mu.user_id AND b.product_id = mu.product_id
WHERE mu.manager_id = 5
GROUP BY mu.user_id, mu.product_id
ORDER BY b.created DESC;
The problem is it doesn't pull the most recent budget. Any suggestions? Thanks!
To accomplish your task you can do as follows:
select b1.user_id,
b1.budget
from budget b1 inner join (
select b.user_id,
b.product_id,
max(created) lastdate
from budget b
group by b.user_id, b.product_id ) q
on b1.user_id=q.user_id and
b1.product_id=q.product_id and
b1.created=q.lastdate
where b1.user_id in
(select user_id from manager_user where manager_id = 5);
I'm assuming here that your (user_id, product_id, created) combination is unique.
For what it's worth, here's the code that returned what I was looking for:
SELECT DISTINCT(b1.id),mu.user_id,mu.product_id,b1.budget,b1.created
FROM budget b1
INNER JOIN (
SELECT b.user_id, b.product_id, MAX(created) lastdate
FROM budget b
GROUP BY b.user_id, b.product_id) q
ON b1.user_id=q.user_id AND
b1.product_id=q.product_id AND
b1.created=q.lastdate
RIGHT JOIN manager_user mu
ON mu.user_id = b1.user_id AND
mu.product_id = b1.product_id
WHERE mu.manager_id = 5;
Thanks for the help Andrea!