I have 2 tables that look like this:
users (uid, name)
-------------------
| 1 | User 1 |
| 2 | User 2 |
| 3 | User 3 |
| 4 | User 4 |
| 5 | User 5 |
-------------------
highscores (user_id, time)
-------------------
| 3 | 12005 |
| 3 | 29505 |
| 3 | 17505 |
| 5 | 19505 |
-------------------
I want to query only for users that have a highscore and only the top highscore of each user. The result should look like:
------------------------
| User 3 | 29505 |
| User 5 | 19505 |
------------------------
My query looks like this:
SELECT user.name, highscores.time
FROM user
INNER JOIN highscores ON user.uid = highscores.user_id
ORDER BY time ASC
LIMIT 0 , 10
Actually this returns multiple highscores of the same user. I also tried to group them but it did not work since it did not return the best result but a random one (eg: for user id 3 it returned 17505 instead of 29505).
Many thanks!
You should use the aggregated function MAX() together with group by clause.
SELECT a.name, MAX(b.`time`) maxTime
FROM users a
INNER JOIN highscores b
on a.uid = b.user_id
GROUP BY a.name
SQLFiddle Demo
Your effort of grouping users was correct. You just needed to use MAX(time) aggregate function instead of selecting only time.
I think you wrote older query was like this:
SELECT name, time
FROM users
INNER JOIN highscores ON users.uid = highscores.user_id
GROUP BY name,time
But actual query should be:
SELECT user.name, MAX(`time`) AS topScore
FROM users
INNER JOIN highscores ON users.uid = highscores.user_id
GROUP BY user.name
Related
Similar to stackoverflow, I have a database of users who vote, comment, and make other actions. I am trying to return a sorted result of the top 10 users who have made the most actions based on the combined count of all of the actions a user has made, along with the actual count of total actions said user made.
Below is my table structure.
Users Table
Typical users data such as an incrementing id, username, email, etc.
| id | username |
-----------------
| 1 | bob |
| 2 | jane |
Votes Table
Has an incrementing id, user_id fk and type of vote made.
| id | user_id | type |
| 1 | 1 | up_vote |
| 2 | 2 | up_vote |
Comments Table
Same as the votes table, typical stuff here.
| id | user_id | comment |
---------------------------------
| 1 | 1 | hello, world |
| 1 | 1 | goodbye, world |
Intended results:
results needed
| total_actions | user_id | username |
-------------------------------------|
| 3 | 1 | bob |
| 1 | 2 | jane |
What I actually know how to do, albeit probably not the most efficient way...
Users sorted by most votes, along with the count
select `users`.*,
(
select count(*)
from `votes`
where `users`.`id` = `votes`.`user_id`
) as `votes_count`
from `users`
order by `votes_count` desc
limit 10
Users sorted by most comments, along with the count
select `users`.*,
(
select count(*)
from `comments`
where `users`.`id` = `comments`.`user_id`
) as `comments_count`
from `users`
order by `comments_count` desc
limit 10
Any help would be greatly appreciated!
You can left join aggregate queries that compute the total votes and comments per user, and sort in the outer query, like so:
select
coalesce(v.cnt, 0) + coalesce(c.cnt, 0) total_actions,
u.id,
u.username
from users u
left join (select user_id, count(*) cnt from votes group by user_id) v
on v.user_id = u.id
left join (select user_id, count(*) cnt from comments group by user_id) c
on c.user_id = u.id
order by total_actions desc
limit 10
While I prefer GMB's method (using LEFT JOIN with each subquery) I'll show here how to combine your existing queries. Just use both correlated subqueries, and add them together to get the total.
select `users`.*,
(
select count(*)
from `votes`
where `users`.`id` = `votes`.`user_id`
) +
(
select count(*)
from `comments`
where `users`.`id` = `comments`.`user_id`
) as total_actions
from `users`
order by total_actions desc
limit 10
So I have a table called the Activities table that contains a schema of user_id, activity
There is a row for each user, activity combo.
Here is a what it might look like (empty rows added to make things easier to look at, please ignore):
| user_id | activity |
|---------|-----------|
| 1 | swimming | -- We want to match this
| 1 | running | -- person's activities
| | |
| 2 | swimming |
| 2 | running |
| 2 | rowing |
| | |
| 3 | swimming |
| | |
| 4 | skydiving |
| 4 | running |
| 4 | swimming |
I would like to basically find all other users with at least the same activities as a given input id so that I could recommend users with similar activities.
so in the table above, if I wanna find recommended users for user_id=1, the query would return user_id=2 and user_id=4 because they engage in both swimming, running (and more), but not user_id=3 because they only engage in swimming
So a result with a single column of:
| user_id |
|---------|
| 2 |
| 4 |
is what I would ideally be looking for
As far as what I've tried, I am kinda stuck at how to get a solid set of user_id=1's activities to match against. Basically I'm looking for something along the lines of:
SELECT user_id from Activities
GROUP BY user_id
HAVING input_user_activities in user_x_activities
where user1_activities is just a set of our input user's activities. I can create that set using a WITH input_user_activities AS (...) in the beginning, what I'm stuck at is the user_x_activities part
Any thoughts?
To get users with the same activities, you can use a self join. Let me assume that the rows are unique:
select a.user_id
from activities a1 join
activities a
on a1.activity = a.activity and
a1.user_id = #user_id
group by a.user_id
having count(*) = (select count(*) from activities a1 where a1.user_id = #user_id);
The having clause answers your question -- of getting users that have the same activities as a given user.
You can easily get all users ordered by similarity using a JOIN (that finds all common rows) and a GROUP BY (to summarize the similarity per user_id) and finally an ORDER BY to return the most similar users first.
SELECT b.user_id, COUNT(*) similarity
FROM activities a
JOIN activities b
ON a.activity = b.activity
WHERE a.user_id = 1 AND b.user_id != 1
GROUP BY b.user_id
ORDER BY COUNT(*) DESC
An SQLfiddle to test with.
Im not even sure what the title of this question should be but lets start out with my data.
I have a table of users who have taken a few lessons while belonging to a particular training center.
lesson table
id | lesson_id | user_id | has_completed
----------------------------------------
1 | asdf3314 | 2 | 1
2 | d13saf12 | 2 | 1
3 | a33adff5 | 2 | 0
4 | a33adff5 | 1 | 1
5 | d13saf12 | 1 | 0
user table
id | center_id | ...
----------------------------------------
1 | 20 | ...
2 | 30 | ...
training center table
id | center_name | ...
----------------------------------------
20 | learn.co | ...
30 | teach.co | ...
I've written a small chunk but am now stuck as I don't know how to proceed. This statement gets the counted total of completed lessons per user. it then figures the average completed value from a center id. if two users belong to a center and have completed 3 lessons and 2 lessons it finds the average of 3 and 2 then returns that.
SELECT
FLOOR(AVG(a.total)) AS avg_completion,
FROM
(SELECT
user_id,
user.center_id,
count(user_id) AS total
FROM lesson
LEFT JOIN user ON user.id = user_id
WHERE is_completed = 1 AND center_id = 2
GROUP BY user_id) AS a;
The question I have is how do I loop through the training centers table and also append average data from similar select statement as above to each center that is queried. I cant seem to pass the center id down to the subquery so there must be a fundamentally different way to achieve the same query but also loop through training centers.
An example of desired result:
center.id | avg_completion | ...training center table
-----------------------------------------------------
20 | 2 | ...
Your main query needs to select a.center_id and then use GROUP BY center_id. You can then join it with the training_center table.
SELECT c.*, x.avg_completion
FROM training_center AS c
JOIN (
SELECT
a.center_id,
FLOOR(AVG(a.total)) AS avg_completion
FROM (
SELECT
user_id
user.center_id,
count(*) AS total
FROM lesson
JOIN user ON user.id = user_id
WHERE is_completed = 1 AND center_id = 2
GROUP BY user_id) AS a
GROUP BY a.center_id) AS x
ON x.center_id = c.id
If I understand correctly:
select u.center_id, count(*) as num_users,
sum(l.has_completed) as num_completed,
avg(l.has_completed) as completed_ratio
from lesson l join
user u
on l.user_id = u.id
group by u.center_id
I've been trying for two days, without luck.
I have the following simplified tables in my database:
customers:
| id | name |
| 1 | andrea |
| 2 | marco |
| 3 | giovanni |
access:
| id | name_id | date |
| 1 | 1 | 5000 |
| 2 | 1 | 4000 |
| 3 | 2 | 1500 |
| 4 | 2 | 3000 |
| 5 | 2 | 1000 |
| 6 | 3 | 6000 |
| 7 | 3 | 2000 |
I want to return all the names with their last access date.
At first I tried simply with
SELECT * FROM customers LEFT JOIN access ON customers.id =
access.name_id
But I got 7 rows instead of 3 as expected. So I understood I need to use GROUP BY statemet as the following:
SELECT * FROM customers LEFT JOIN access ON customers.id =
access.name_id GROUP BY customers.id
As far I know, GROUP BY combines using a random row. In fact I got unordered access dates with several tests.
Instead I need to group every customer id with its corresponding latest access! How this can be done?
You have to get the latest date from the access table with a group by on the the name_id, then join this result with the customer table. Here is the query:
select c.id, c.name, a.last_access_date from customers c left join
(select id, name_id, max(access_date) last_access_date from access group by name_id) a
on c.id=a.name_id;
Here is a DEMO on sqlfiddle.
I think this is what you'd like to achieve:
SELECT c.id, c.name, max(a.date) last_access
FROM customers c
LEFT JOIN access a ON c.id = a.name_id
GROUP BY c.id, c.name
The LEFT join will return all entries in table customers regardless if the join criteria (c.id = a.name_id) is satisfied. This means that you might get some NULL entries.
Example:
Simply add a new row in the customers table (id: 4, name: manuela). The output will have 4 rows and the newest row will be (id: 4, last_access: null)
I would do this using a correlated subquery in the ON clause:
SELECT a.*, c.*
FROM customers c LEFT JOIN
access a
ON c.id = a.name_id AND
a.DATE = (SELECT MAX(a2.date) FROM access a2 WHERE a2.name_id = a.name_id);
If this statement is true:
I need to group every customer id with its corresponding latest access! How this can be done?
Then you can simply do:
select a.name_id, max(a2.date)
from access a
group by a.name_id;
You do not need the customers table because:
All customers are in access, so the left join is not necessary.
You need no columns from customers.
I have the following (simplified) three tables:
user_reservations:
id | user_id |
1 | 3 |
1 | 3 |
user_kar:
id | user_id | szak_id |
1 | 3 | 1 |
2 | 3 | 2 |
szak:
id | name |
1 | A |
2 | B |
Now I would like to count the reservations of the user by the 'szak' name, but I want to have every user counted only for one szak. In this case, user_id has 2 'szak', and if I write a query something like:
SELECT sz.name, COUNT(*) FROM user_reservations r
LEFT JOIN user_kar k ON k.user_id = r.user_id
LEFT JOIN szak s ON r.szak_id = r.id
It will return two rows:
A | 2 |
B | 2 |
However I want to every reservation counted to only one szak (lets say the highest id only). I tried MAX(k.id) with HAVING, but seems uneffective.
I would like to know if there is a supported method for that in MySQL, or should I first pick all the user ID-s on the backend site first, check their maximum kar.user_id, and then count only with those, removing them from the id list, when the given szak is counted, and then build the data back together on the backend side?
Thanks for the help - I was googling around for like 2 hours, but so far, I found no solution, so maybe you could help me.
Something like this?
SELECT sz.name,
Count(*)
FROM (SELECT r.user_id,
Ifnull(Max(k.szak_id), -1) AS max_szak_id
FROM user_reservations r
LEFT OUTER JOIN user_kar k
ON k.user_id = r.user_id
GROUP BY r.user_id) t
LEFT OUTER JOIN szak sz
ON sz.id = t.max_szak_id
GROUP BY sz.name;