mysql count only for distinct values in joined query - mysql

WOW! That's a weird title, but I'm happy you're looking, cause I couldn't find another way to say it.
I have a table of people and a table of links to photos and videos.
I join the people to the media, and of course a person can have more than one piece of media.
I am attempting to grab the first 30 people and all of their media in one query, but when I say LIMIT 0,30. Of course I'll only get the first 30 media files, which could be 10 people.
Before I make the query, I don't know how many media files each person is going to have. Is there a way I can say LIMIT DISTINCT(uid 0,30)????
or something like that?

Can you use subqueries in your version of MySQL?
select *
from media m
inner join
( select uid
from users_tbl
limit 0,30) map
on map.uid = m.uid
inner join users_tbl u
on u.uid = m.uid

FWIW, here's another query that should return the same result:
SELECT *
FROM media m INNER JOIN users_tbl u USING (uid)
WHERE u.uid IN (SELECT uid FROM users_tbl ORDER BY uid LIMIT 0,30);
Note that when you use LIMIT, you should always use an ORDER BY. Otherwise there is no guarantee which thirty users you'll get.

Related

Select top 10 authors with most articles published in MySQL really slow

I try to make a list of users having the most articles published (or something like that).
So I wrote something like this:
SELECT u.id, COUNT(a.id) AS articles
FROM users u
LEFT JOIN articles a ON a.user_id = u.id
GROUP BY u.id
ORDER BY articles DESC
LIMIT 10
The 'explain' command shows it 'Using join buffer (Block Nested Loop)' and 'Using temporary; Using filesort'.
It costs really long time to get the result.
I know the reason that causes this problem. But I don't know how to solve it.
Also, I wanted to sort records by user's register time instead of published articles as an option.
And I know I can add a column to users table accumulates the articles number, but I'm not be able to change the original program.
It seems to me that this is the only way to make the list. But also the impossible way to retrieve the result before my time.
Is there another faster way to get a top 10 list based on join queries?
Any suggestion would be appreciated.
Try this:
SELECT
u.id, t.articles
FROM USERS u
LEFT JOIN
(
SELECT user_id, COUNT(id) AS `articles`
FROM articles
GROUP BY user_id
) t ON u.id = t.user_id
ORDER BY t.articles DESC
LIMIT 10

use COUNT(*) values from one table to another

Suppose I have two tables, users and posts. Posts has the following fields, userid, postid, etc and userid can appear multiple times as one user can write multiple posts....I'm just trying sort the users table based off the # of occurrences per userid in the posts table. I can get the # of occurrences per user using this
SELECT userid, COUNT(*)
FROM posts
GROUP BY userid;
I would like to use the values under COUNT(*) column, maybe add it to my other table because then I can simply to something like this
SELECT * FROM users
ORDER BY newcolumn ASC;
but I'm having trouble doing that. Or can I do it without having to add an extra column? Hints please. Thanks
Left join is the key here!
SELECT users.userid,count(posts.userid) AS total_count
FROM users
LEFT JOIN posts on posts.userid = users.userid
GROUP BY users.userid
ORDER BY total_count DESC;
We are taking the left join on two tables with same user_id and we are counting the total number of posts per user using group by. Finally sort by count and show results.
try an left join:
select users.userid, [user fields],count(postid) as posts_count
from users
left join posts on posts.userid = users.userid
group by users.userid,[user fields]
order by posts_count desc.
You want to select users (FROM users) but you want to sort based on criteria in another table (COUNT(*) FROM posts) -- therefore you need to use a JOIN
Off-hand I can't seem to recall if "JOIN" or "RIGHT JOIN" or "FULL JOIN" is what you need if you wanted to get a cartesian product of the tables then group and aggregate on a single field, but I can avoid the need to remember with a subquery (hopefully someone will soon post a smaller and smarter answer):
SELECT users.* FROM users
JOIN (
SELECT userid, COUNT(*) as count
FROM posts
GROUP BY userid
) as subquery ON users.id = subquery.userid
ORDER BY subquery.count
Note: I haven't tested this query, but it looks good to me. Again: hopefully someone will post a better answer soon as I'm not doing my due dilligence, but you definitely need a JOIN :)
You could add a post_count column to the users table, but you would also have to update that count column every time a user creates a new post and you would have to build that logic into your application.
Otherwise, it looks like the answer from FallAndLearn will get you what you need.

is it possible to write this mysql statement in a better way? cleanere? more readable? faster?

I have two tables, users and results.
Results contains column user_id same as users table.
I want to grab results table, sum results_value column, and than use user_id to grab additional info from users table.... I came up with this:
SELECT results.user_id, SUM(results.result_value), users.user_name, users.user_pic, users.user_level
FROM results, users
WHERE users.user_id = results.user_id
GROUP BY results.user_id
ORDER BY SUM(results.result_value) DESC
LIMIT 4
It actually works, but being a complete mysql beginner, I'm wondering if I'm doing something stupid, or maybe it works but there is a better way (faster) way of doing the same thing?
Your method is actually fine. It is possible to write it without GROUP BY, it is probably easier to understand:
SELECT
u.user_id,
( SELECT sum(r.result_value)
FROM results r
WHERE r.user_id = u.user_id
) AS rsum,
u.user_name,
u.user_pic,
u.user_level
FROM users u
ORDER BY 2 DESC
LIMIT 4
As you may notice, using table aliases helps a lot with readability of your queries.
Basics look fine. Main thing is to make sure you have suitable indexes. Also you should have all the non aggregate columns named in the GROUP BY clause (not 100% necessary in MySQL, but is in some other flavours of SQL, and not doing this can result in indeterminate values being returned for those columns).
You can also do the JOIN using more modern syntax:-
SELECT results.user_id, SUM(results.result_value) AS Asum, users.user_name, users.user_pic, users.user_level
FROM results
INNER JOIN users
ON users.user_id = results.user_id
GROUP BY results.user_id, users.user_name, users.user_pic, users.user_level
ORDER BY Asum DESC
LIMIT 4

The least amount of code possible for this MySQL query?

I have a MySQL query that:
gets data from three tables linked by unique id's.
counts the number of games played in each category, from each user
and counts the number of games each user has played that fall under the "fps" category
It seems to me that this code could be a lot smaller. How would I go about making this query smaller. http://sqlfiddle.com/#!2/6d211/1
Any help is appreciated even if you just give me links to check out.
Generally it's a good idea to have your join logic as part of the [Inner|Left] Join clause, rather than as part of the Where clause. In your case of simplifying the query, this cleans up your Where clause so that the query processor doesn't apply filter conditions too early, which restricts what you want to do in more complex parts of the query (and impacts the overall performance of the query).
By refactoring the join conditions, we can reduce the query to its core join across the three tables, and then add the join to the specialised subquery where the aggregation occurs. This results in only one nested query, which joins across the fewest tables needed.
Here's what I came up with:
SELECT
u.user_id
,pg.game_id
,u.user
,g.game
,g.game_cat
,ga.cat_count
,ga.fps_count
FROM users u
inner join played_games pg
on u.user_id = pg.user_id
inner join games g
on pg.game_id = g.id
inner join
(
select
ipg.user_id
,ig.game_cat
,count(ig.game) cat_count
,sum(case when ig.game_cat = 'fps' then 1 else 0 end) fps_count
from played_games ipg
inner join games ig
on ipg.game_id = ig.id
group by
ipg.user_id
,ig.game_cat
) ga
on g.game_cat = ga.game_cat
and pg.user_id = ga.user_id
order by
ga.fps_count desc
,u.user
,ga.cat_count desc;
One difference between the original query (apart from the slight rename) is that the fps_count field has a value of 0 instead of NULL for players who haven't played a single FPS game. Hopefully this isn't so critical, but rather helps to add meaning to the query.
Lastly, I'm not sure about the context of how this is going to be used. In my opinion it's probably trying to do too much in both listing every game played by every user (one objective) and summarising the categories of games played by each user (a separate objective). This means that the summary details are being repeated multiple times, e.g. for users playing multiple games of a particular category, which may not be ideal. My recommendation would be to separate these out into two separate queries, though I don't know whether that would meet your specific needs.
Hope this helps.
I was thinking whether to provide d_mcg solution or this one. I decided to go for this one. I was wondering which one would be faster. That's something you can try and tell us :)
select u.user_id, pg.game_id, u.user, g.game, g.game_cat,
(select count(*) from played_games pg2
join games g2 on pg2.game_id = g2.id
where pg2.user_id = pg.user_id and g2.game_cat = g.game_cat) cat_count,
(select count(*) from played_games pg3
join games g3 on pg3.game_id = g3.id
where pg3.user_id = pg.user_id and g3.game_cat = g.game_cat and
g.game_cat = 'fps') order_count
from users u
left join played_games pg on u.user_id = pg.user_id
join games g on pg.game_id = g.id
order by order_count desc, u.user, cat_count desc

Mysql Multiple table select with condition

I have a noob question but rather a troublesome one for me. I am using SELECT on three tables the middle one of which is realtional (Holds relations - ID of user against ID of Place), the first is a table of users, the last of places. I have written this perfectly woking query
$query = "SELECT users.Username,usrxplc.User,places.Name
FROM users,usrxplc,places
WHERE usrxplc.Place=places.ID AND usrxplc.User=users.ID"
That spits out all places associated with all users. Fine, but I would like to limit it only to a certain user. Seems simple, but I am stuck.
You use a WHERE clause to filter the results, so just add a clause for users.ID:
select users.Username,
usrxplc.User,
places.name
from users,
usrxplc,
places
where usrxplc.Place = places.ID
and usrxplc.User = users.ID
and users.ID = 123
Just felt the need to post the alternative - instead of selecting and all tables you can use INNER JOIN to join one table onto another
SELECT
users.Username,
places.Name
FROM users
INNER JOIN usrxplc ON usrxplc.User=users.ID
INNER JOIN places ON places.ID = usrxplc.Place
WHERE users.ID = 111
It's functionally the same as the other answer, however when you get onto more complex queries and tables you will find that using JOINs allows for greater optimisation as you are able to further limit the rows each individual JOIN gets, for example the following is also valid, where the User row is limited before joining onto other tables
SELECT
users.Username,
places.Name
FROM places
INNER JOIN usrxplc ON usrxplc.Place = places.ID
INNER JOIN users ON users.ID = usrxplc.User AND users.ID = 111
In more complicated queries, or if these tables were to be far larger, this would in turn offer a more optimal query generally speaking