mySql INNER JOIN, MAX & DISTINCT - mysql

I'm looking to return one row for each user of type "student" displaying their "name" and their latest "score" (in reverse chronological order).
I have two tables users & services
users Table
id name type
---|-------|-----
1 | Bob | student
2 | Dave | student
3 | Larry | student
4 | Kevin | master
services table
id score userId date
---|--------|-------|------------
1 | 14 | 1 | 2014-09-04
2 | 99 | 3 | 2014-09-03
3 | 53 | 2 | 2014-09-07
4 | 21 | 1 | 2014-09-08
5 | 79 | 2 | 2014-09-08
6 | 43 | 3 | 2014-09-10
7 | 72 | 3 | 2014-09-10
8 | 66 | 2 | 2014-09-01
9 | 43 | 3 | 2014-08-22
10 | 26 | 1 | 2014-08-22
Desired Result
id scores name date
---|--------|-------|------------
3 | 43 | Larry | 2014-09-10
1 | 21 | Bob | 2014-09-08
2 | 79 | Dave | 2014-09-08
What I have tried is:
SELECT users.id, users.name, services.date, services.score
FROM users
JOIN services ON users.id = services.userId
WHERE users.type='student'
ORDER BY services.date DESC
But this always returns the last date in the table for each user.
So i decided to try and tackle it from the other end like this:
SELECT servicesTemp.date, servicesTemp.score
FROM services servicesTemp
INNER JOIN
(SELECT userId, MAX(date) AS MaxExpDate
FROM services
GROUP BY clientId) servicesTempGrp
ON servicesTemp.userId = servicesTempGrp.userId
AND servicesTemp.MaxDate = servicesTempGrp.MaxDate
But realised that i would end up with duplicates if the dates were ever the same and i can only return one row per user (and double grouping didn't work).
I think i'm now over complicating this, so a life line would be much appreciated.

try:
SELECT users.id, users.name, services.date, services.score
FROM users
JOIN services ON users.id = services.userId
WHERE users.type='client'
AND services.date = (SELECT MAX(date) from services where userID = users.id)
ORDER BY services.date DESC

You can guarantee one row by using the substring_index()/group_concat() trick:
SELECT u.id, u.name, max(s.date) as date,
substring_index(group_concat(s.score order by date desc), ',', 1) as score
FROM users u JOIN
services s
ON u.id = s.userId
WHERE u.type = 'client'
GROUP BY u.id, u.name
ORDER BY s.date DESC;
Without using group by, another option for getting only one row per user is to use variables. Or, if you know the ids are being assigned sequentially, use the id instead of date:
SELECT u.id, u.name, s.date, s.score
FROM users u INNER JOIN
services s
on u.userId = s.userId INNER JOIN
(SELECT userId, MAX(id) AS MaxId
FROM services
GROUP BY userId
) smax
ON s.userId = smax.userId and s.Id = smax.MaxId
WHERE u.type = 'client';

Related

Select COUNT with sub-query or separate query

I'm using DataTables to display the data. It requires the total count of rows, So which approach is better for this case?
1- Sub-query:
SELECT u.id, u.name, (SELECT COUNT(id) FROM users WHERE active = 1) AS total_count FROM employees e JOIN users u ON u.id = e.user_id WHERE u.active = 1
This would return:
___________________________
| id | name | total_count |
|____|______|_____________|
| 1 | John | 7 |
| 2 | Mark | 7 |
| .. | .. | 7 |
|____|______|_____________|
2- Separate query:
SELECT u.id, u.name FROM employees e JOIN users u ON u.id = e.user_id WHERE u.active = 1
SELECT COUNT(id) FROM users WHERE active = 1
This 1st query would return:
____________
| id | name |
|____|______|
| 1 | John |
| 2 | Mark |
| .. | .. |
|____|______|
The 2nd one would return:
_____________
| COUNT(id) |
|___________|
| 7 |
|___________|
If the dataset is small then the 1st query wouldn't harm.The only drawback is since inline view is part of select statement it will get executed for each record being returned by the join between employees and users.
Here is a better option from performance standpoint,the inline view represents a table and the total_count can now be part of select statement :
SELECT u.id, u.name,total_count.tot_count
FROM (SELECT COUNT(id) tot_count FROM users WHERE active = 1) AS total_count,
employees e JOIN users u
ON u.id = e.user_id WHERE u.active = 1;

Query to select from one table and then from the other, based on results

I have been trying to select all things from one table and then for each result select from another table only one newest result using MySQL.
The first table is a standard one, with AI id and text name
users
+----+-------+
| id | name |
+----+-------+
| 1 | John |
| 2 | Peter |
+----+-------+
then there is the second one with AI id, int user_id, text action and datetime date
actions
+----+---------+--------+------------+-----+
| id | user_id | action | date | str |
+----+---------+--------+------------+-----+
| 7 | 2 | drink | 2019-01-10 | 5 |
| 6 | 1 | sleep | 2019-02-14 | -2 |
| 5 | 2 | walk | 2019-04-24 | 4 |
| 4 | 1 | jump | 2019-03-14 | 3 |
| 3 | 2 | talk | 2019-04-30 | -8 |
| 2 | 2 | train | 2019-04-14 | -1 |
| 1 | 1 | drive | 2019-04-01 | 1 |
+----+---------+--------+------------+-----+
So now I want to select all from table_users and for each found row search table_actions and find only newest one, either based on id or date.
So it would look either like this (by id)
[0] => ['user_id'] = 1
['name'] = John
['action'] = sleep
['date'] = 2019-02-14
['str'] = -2
[1] => ['user_id'] = 2
['name'] = Peter
['action'] = drink
['date'] = 2019-01-10
['str'] = 5
or like this
[0] => ['id'] = 1
['name'] = John
['table_actions'] => ['id'] = 6
['user_id'] = 1
['action'] = sleep
['date'] = 2019-02-14
['str'] = -2
this sounds easy, but I tried few things and nothing worked like this. The closes one was with something like (dont have exact version on my hand, just remembering from on top of my head):
SELECT users.*, actions.*
FROM users
LEFT JOIN actions ON users.id = (
SELECT user_id FROM actions
WHERE users.id = actions.user_id
LIMIT 1
)
GROUP BY actions.user_id
with that I would get all results from users and then for each get one result from actions, but that result from actions would not be the newest one, apparently it groups as it likes to, I tried MAX(actions.id), but I have got no luck with that.
Does anyone know the solution ? for now I have to take all from users and for each result take another query in my php code and I feel there is an elegant and faster way to do that.
With this query:
select
user_id,
max(date) date
from actions
group by user_id
you get the latest date for each user and then you must join to it the 2 tables:
select
u.id, u.name, a.id, a.action, a.date, a.str
from users u
inner join actions a on a.user_id = u.id
inner join (
select
user_id,
max(date) date
from actions
group by user_id
) g on g.user_id = a.user_id and g.date = a.date
If you want the latest results by id and not by date:
select
u.id, u.name, a.id, a.action, a.date, a.str
from users u
inner join actions a on a.user_id = u.id
inner join (
select
user_id,
max(id) id
from actions
group by user_id
) g on g.user_id = a.user_id and g.id = a.id

MySQL How to count multiple entries while maintaining unique fileds

I have 3 tables that look like this:
acc_prop
id | pid | uid
1 | 10 | 1
2 | 11 | 1
3 | 12 | 1
cal
id | pid
1 | 10
2 | 11
3 | 12
price
cid | rate
1 | 100
2 | 99
3 | 130
I want to create a query that returns a pid, a count of uid's with the same uid, and the rate for that pid.
expected result
pid | uid_count | rate
10 | 3 | 100
11 | 3 | 99
12 | 3 | 130
my query looks like this
SELECT
cal.pid,
count(ap3.uid) as uid_count,
price.rate
FROM
price
JOIN
cal on cal.id = price.cid
JOIN
acc_prop ap using(pid)
JOIN
acc_prop ap2 on ap2.uid = ap.uid
JOIN
acc_prop ap3 on ap3.uid = ap2.uid
group by ap3.pid;
But it returns
the incorrect count
the incorrect pid list
the incorrect rate
actual result
pid | uid_count | rate
10 | 9 | 100
10 | 9 | 100
I think what you are after is this, viz to pre-calculate the number of users in acc_prop as a derived table, which you can then join through to the rest of the query:
SELECT
cal.pid,
UserCount.Cnt,
price.rate
FROM
price
JOIN cal on cal.id = price.cid
JOIN acc_prop ap using(pid)
JOIN
(
SELECT uid, COUNT(*) AS Cnt
FROM acc_prop
GROUP BY uid
) UserCount
ON ap.uid = UserCount.uid;
SqlFiddle here
I think cal is not needed here, do you tried this:
SELECT
acc_prop.pid, (SELECT COUNT(*) FROM acc_prop WHERE uid = uid) AS uid_count, price.rate
FROM
acc_prop
INNER JOIN price
ON acc_prop.id = price.cid

mysql get user rank using inner join

I have three tables as following. And i want to get user with highest rank.
1) users table as
id | user_id | created_at
1 | 100 | 2014-11-07 02:54:09
2 | 102 | 2014-11-08 03:52:40
3 | 103 | 2014-11-10 02:47:26
4 | 104 | 2014-11-11 02:54:48
5 | 105 | 2014-11-14 03:11:23
6 | 105 | 2014-11-15 00:56:34
2) user_profile table as
id | user_id | rank
1 | 100 | 100
2 | 102 | 500
3 | 103 | 10
4 | 104 | 0
5 | 105 | 11
6 | 105 | 1000
3) user_followers table as
id | user_id | followers
1 | 100 | 10
2 | 102 | 20
3 | 103 | 30
4 | 104 | 40
5 | 105 | 0
6 | 105 | 50
Now my query is i want to get list of users short by highest rank in table2. In case of tie user with the highest followers in table3 will win. In case of same followers user who is created first will win.
And another one i want to find user rank with same logic passing by user id.
I already tried something like
SET #i=0;
SELECT user_id, rank, #i:=#i+1 AS rank FROM user_profile ORDER BY rank DESC
Arion's answer looked like this...
SELECT
users.*
FROM
users
JOIN user_profile
ON users.user_id = user_profile.user_id
JOIN user_followers
ON user_profile.user_id=user_followers.user_id
ORDER BY
user_profile.rank DESC,
user_followers.followers DESC,
users.created_at DESC
...but this seems a little closer to what you're after...
SELECT u.user_id
, u.created_at
, up.rank
, uf.followers
, #i:=#i+1 corrected_rank
FROM users u
LEFT
JOIN user_profile up
ON up.user_id = u.user_id
LEFT
JOIN user_followers uf
ON uf.user_id = u.user_id
CROSS
JOIN (SELECT #i:=1) v
ORDER
BY rank DESC
, followers DESC
, created_at ASC;
SET #rank = 0;
SELECT
#rank := #rank + 1 AS rank, *
FROM
(
SELECT users.user_id, user_profile.rank, user_followers.followers, users.created_at
FROM users
LEFT JOIN user_profile ON users.user_id = user_profile.user_id
LEFT JOIN user_followers ON users.user_id = user_followers.user_id
ORDER BY user_profile.rank DESC, user_followers.followers DESC, users.created_at ASC
)

JOIN and GROUP_CONCAT with three tables

I have three tables:
users: sports: user_sports:
id | name id | name id_user | id_sport | pref
---+-------- ---+------------ --------+----------+------
1 | Peter 1 | Tennis 1 | 1 | 0
2 | Alice 2 | Football 1 | 2 | 1
3 | Bob 3 | Basketball 2 | 3 | 0
3 | 1 | 2
3 | 3 | 1
3 | 2 | 0
The table user_sports links users and sports with an order of preference (pref).
I need to make a query that returns this:
id | name | sport_ids | sport_names
---+-------+-----------+----------------------------
1 | Peter | 1,2 | Tennis,Football
2 | Alice | 3 | Basketball
3 | Bob | 2,3,1 | Football,Basketball,Tennis
I have tried with JOIN and GROUP_CONCAT but I get weird results.
Do I need to do a nested query?
Any ideas?
Its not particularly difficult.
Join the three tables using the JOIN clause.
Use Group_concat on the fields you're interested in.
Don't forget the GROUP BY clause on the fields you're not concatenating or weird things will happen
SELECT u.id,
u.Name,
Group_concat(us.id_sport order by pref) sport_ids,
Group_concat(s.name order by pref) sport_names
FROM users u
LEFT JOIN User_Sports us
ON u.id = us.id_user
LEFT JOIN sports s
ON US.id_sport = s.id
GROUP BY u.id,
u.Name
DEMO
Update LEFT JOIN for when the user doesn't have entries in User_Sports as per comments
I think this is just a simple join and aggregation:
select u.id, u.name, group_concat(s.name order by pref separator ',')
from user_sports us join
users u
on us.id_user = u.id join
sports s
on us.id_sport = s.id
group by u.id, u.name