+-----+-------+-----+
| id | Name |Votes|
+-----+-------+-----+
| 1 | Joe | 36 |
| 2 | John | 34 |
| 3 | Mark | 42 |
| 4 | Ryan | 29 |
| 5 | Jay | 36 |
| 6 | Shawn | 39 |
+-----+-------+-----+
For this example, what I want is to retrieve the rows with the first 3 highest votes. However, if you'll notice, there are two rows with the same vote count. So this should be the result:
+-----+-------+-----+
| id | Name |Votes|
+-----+-------+-----+
| 3 | Mark | 42 |
| 6 | Shawn | 39 |
| 1 | Joe | 36 |
| 5 | Jay | 36 |
+-----+-------+-----+
How to achieve this?
You will have to perform an INNER JOIN, using the table back on itself. First, you want to select the top 3 unique/distinct scores, and this can be done by using:
SELECT DISTINCT Votes FROM mytable ORDER BY Votes DESC LIMIT 3
Now that you have obtained the top 3 scores, you want to join it back to the original table:
SELECT t1.* FROM mytable AS t1
INNER JOIN
(SELECT DISTINCT Votes FROM mytable ORDER BY Votes DESC LIMIT 3) AS topvotes
ON
topvotes.Votes = t1.Votes
ORDER BY t1.Votes DESC
Refer to a simple diagram for the strategy:
For this query to be efficient, you will want to index the Votes column so that the subquery can fish out distinct votes quickly ;)
Here is a proof-of-concept SQLfiddle: http://sqlfiddle.com/#!9/c78f0/10
Probably not the most efficient, but I think this should work:
SELECT * FROM scores WHERE score IN(SELECT score FROM scores ORDER BY score DESC LIMIT 3)
Although this can yield an error about limit not being supported in subqueries.
A workaround;
SELECT * FROM scores WHERE score IN(SELECT * FROM (SELECT score FROM scores ORDER BY score DESC LIMIT 3) AS t)
Related
We are looking to return rows of a query as groups and displaying all entries of the group in the sort order. Randomly based on the set_id... and then in order by the sort_id.
So, randomly it will show:
Carl,
Phil,
Wendy,
Tina,
Rick,
Joe
or
Tina,
Rick,
Joe,
Carl,
Phil,
Wendy
This query is always showing Tina/Rick/Joe first
SELECT * FROM products ORDER BY set_id, rand()
Any help would be appreciated
+---------+--------+-------+----------+
| id | set_id | name | sort_id |
+---------+--------+-------+----------+
| 1 | AA |Rick | 2 |
| 2 | BB |Carl | 1 |
| 3 | AA |Joe | 3 |
| 4 | AA |Tina | 1 |
| 5 | BB |Phil | 2 |
| 6 | BB |Wendy | 3 |
+---------+--------+-------+----------+
if you need a random comma separated name list this will do the trick.
This will keep the groups and the correct sorting within the group.
Query
SELECT
GROUP_CONCAT(Table_names_rand.names) as names
FROM (
SELECT
*
FROM (
SELECT
GROUP_CONCAT(name ORDER BY sort_id) as names
FROM
Table1
GROUP BY
set_id
)
AS Table1_names
ORDER BY
RAND()
)
AS Table_names_rand
Result
| names |
|-------------------------------|
| Carl,Phil,Wendy,Tina,Rick,Joe |
or
| names |
|-------------------------------|
| Tina,Rick,Joe,Carl,Phil,Wendy |
demo http://www.sqlfiddle.com/#!9/487ac9/9
if you need random names as records output.
Query
SELECT
Table1.name
FROM
Table1
CROSS JOIN (
SELECT
GROUP_CONCAT(Table_names_rand.names) as names
FROM (
SELECT
*
FROM (
SELECT
GROUP_CONCAT(name ORDER BY sort_id) as names
FROM
Table1
GROUP BY
set_id
)
AS Table1_names
ORDER BY
RAND()
)
AS Table_names_rand
)
AS Table_names_rand
ORDER BY
FIND_IN_SET(name, Table_names_rand.names)
Result
| name |
|-------|
| Carl |
| Phil |
| Wendy |
| Tina |
| Rick |
| Joe |
or
| name |
|-------|
| Tina |
| Rick |
| Joe |
| Carl |
| Phil |
| Wendy |
demo http://www.sqlfiddle.com/#!9/487ac9/28
If we strip away the randomness of the gorup ordering, your query would look like this:
SELECT
*
FROM
products
ORDER BY
set_id,
sort_id;
The ordering by set_id is necessary to "group" the results, without really grouping them. You do not want to group them, because then the rows with the same group would be aggregated, meaning that only one row per group would be put out.
Since you only want to randomize the groups, you need to write another query that assigns a random number to each group, like the one below:
SELECT
set_id,
RAND() as 'rnd'
FROM
products
GROUP BY
set_id
The GROUP BY clause makes sure, that each group is only selected once. The resultset will look like this:
| set_id | priority |
+--------+---------+
| AA | 0.21 |
| BB | 0.1 |
With that result we can then randomize the output, by combining both queries with a JOIN on the set_id field. This will add the randomly generated number from the second query to the result set of the first query and therefore extend the static set_id with the randomized, but still for all group members equal, rnd:
SELECT
products.*
FROM
products
JOIN (
SELECT
set_id,
RAND() as 'rnd'
FROM
products
GROUP BY
set_id
) as rnd ON rnd.set_id = products.set_id
ORDER BY
rnd.rnd,
products.set_id,
products.sort_id;
Keep in mind, that it is important to still group on products.set_id, because it may be possible that two groups get the same random number assigned. If the result would not be ordered by products.set_id those groups members would then be merged.
I have a SQL table user_game which contains the games that a user owns:
| id | user_id | game_id |
|----|---------|---------|
| 83 | 1 | 1 |
| 84 | 1 | 2 |
| 85 | 1 | 3 |
| 86 | 2 | 2 |
| 87 | 2 | 3 |
| 88 | 2 | 4 |
| 89 | 3 | 2 |
I am trying to count the number of users which have 1 game, 2 games, 3 games.. etc.
User 1 has 3 games, User 2 has 3 games, and User 3 has 1 game. Therefore these are the results I want to achieve:
| no_of_games | COUNT(no_of_games) |
|-------------|--------------------|
| 1 | 1 |
| 2 | 0 |
| 3 | 2 |
COUNT(no_of_games) is the number of users that have that number of games.
I can individually get the number of users for each no_of_games with this query:
-- Select no. of users with 1 game
SELECT no_of_games, COUNT(no_of_games)
FROM
(
-- Select no. of games each user has
SELECT user_id, COUNT(1) as no_of_games
FROM user_game
GROUP BY user_id
) as A
WHERE no_of_games = 1;
which gives the results:
| no_of_games | COUNT(no_of_games) |
|-------------|--------------------|
| 1 | 1 |
However I have to change the no_of_games = 1 to 2, 3, 4... manually and UNION them with this solution and I can't do it for ~60 cases.
Is there a simpler way to achieve this?
Your problem is a bit tricky, because groups of games which do not appear in your data with a certain frequency (e.g. 2) will not appear in the result set just using your original table. In the query below, I use a second table called nums which simply contains the sequence 1 through 10 representing counts of number of games. By using a LEFT JOIN we can retain each game count in the final result set.
SELECT t1.no_of_games,
COALESCE(t2.no_of_games_count, 0) AS no_of_games_count
FROM nums t1
LEFT JOIN
(
SELECT t.no_of_games, COUNT(*) AS no_of_games_count
FROM
(
SELECT COUNT(*) AS no_of_games
FROM user_game
GROUP BY user_id
) t
GROUP BY t.no_of_games
) t2
ON t1.no_of_games = t2.no_of_games
ORDER BY t1.no_of_games
And here is the definition I used for nums:
CREATE TABLE nums (`no_of_games` int);
INSERT INTO nums (`no_of_games`)
VALUES
(1),(2),(3),(4),(5),(6),(7),(8),(9),(10);
Demo here:
SQLFiddle
You can find count of games for each user and then find count of users for each count of games.
select cnt no_of_games, count(*) cnt_no_of_games
from(
select user_id, count(*) cnt
from your_table
group by user_id
) t group by cnt;
I have a table of users with their scores for each level of a game:
id | user_id | level | score
1 | David | 1 | 20
2 | John | 1 | 40
3 | John | 2 | 30
4 | Mark | 1 | 60
5 | David | 2 | 10
6 | David | 3 | 80
7 | Mark | 2 | 20
8 | John | 3 | 70
9 | David | 4 | 50
10 | John | 4 | 30
What is the SQL query needed to get for each level, who has the highest score?
The result should be:
id | user_id | level | score
4 | Mark | 1 | 60
3 | John | 2 | 30
6 | David | 3 | 80
9 | David | 4 | 50
Thank you
If you want to get ties, then you can do something like this:
select s.*
from scores s
where s.score = (select max(s2.score) from scores s2 where s2.level = s.level);
You could get one row per level by aggregating this:
select s.level, s.score, group_concat(s.user_id)
from scores s
where s.score = (select max(s2.score) from scores s2 where s2.level = s.level)
group by s.level, s.score;
This combines the users (if there is more than one) into a single field.
order by score desc in sub query, then select max(score) group by level.
select id, user_id , level , max(score) as score
from
(select * from scores order by score desc)A
group by level
If you only want the user, who reached the highest score first (no ties per level):
select *
from users u1
where id = (
select id
from users u2
where u2.level = u1.level
order by score desc, id asc
limit 1
)
You should have indexes (id) and (level, score, id)
Say if I have a table similar to this but including more columns and more rows (These are the only relevant ones):
+-------+----+
| name | id |
+-------+----+
| james | 1 |
| james | 2 |
| james | 3 |
| adam | 4 |
| max | 5 |
| adam | 6 |
| max | 7 |
| adam | 8 |
+-------+----+
How could I get it so that it would only show the max(id) from each name like:
+-------+----+
| name | id |
+-------+----+
| adam | 8 |
| max | 7 |
| james | 3 |
+-------+----+
I currently just have this
"select * from table order by id desc"
but this just shows the latest ids. I only want to be able to see one of each name.
So basically show only the highest id of each name
You would use aggregation and max():
select name, max(id)
from table t
group by name
order by max(id) desc
limit 40;
EDIT:
If you need select * with the highest id, then use the not exists approach:
select *
from table t
where not exists (select 1 from table t2 where t2.name = t.name and t2.id > t.id)
order by id desc
limit 40;
The "not exists" essentially says: "Get me all rows in the table where there is no other row with the same name and a higher id". That is a round-about way of getting the maximum row.
One way to achieve this is to leverage a non-standard GROUP BY extension in MySQL
SELECT *
FROM
(
SELECT *
FROM table1
ORDER BY id DESC
) q
GROUP BY name
-- LIMIT 40
or another way is to grab a max id per name first and then join back to your table to fetch all other columns
SELECT t.*
FROM
(
SELECT MAX(id) id
FROM table1
GROUP BY name
-- LIMIT 40
) q JOIN table1 t
ON q.id = t.id
ORDER BY name;
Output:
| NAME | ID |
|-------|----|
| adam | 8 |
| james | 3 |
| max | 7 |
Here is SQLFiddle demo
I have some MySQL results like this:
---------------------------
| name | something_random |
---------------------------
| john | ekjalsdjalfjkldd |
| alex | akjsldfjaekallee |
| alex | jkjlkjslakjfjflj |
| alex | kajslejajejjaddd |
| bob | ekakdie33kkd93ld |
| bob | 33kd993kakakl3ll |
| paul | 3k309dki595k3lkd |
| paul | 3k399kkfkg93lk3l |
etc...
This goes on for 1000's of rows of results. I need to limit the number of results to the first 50 unique names. I think there is a simple solution to this but I'm not sure.
I've tried using derived tables and variables but can't quite get there. If I could figure out how to increment a variable once every time a name is different I think I could say WHERE variable <= 50.
UPDATED
I've tried the Inner Join approach(es) suggested below. The problem is this:
The subselect SELECT DISTINCT name FROM testTable LIMIT 50 grabs the first 50 distinct names. Perhaps I wasn't clear enough in my original post, but this limits my query too much. In my query, not every name in the table is returned in the result. Let me modify my original example:
----------------------------------
| id | name | something_random |
----------------------------------
| 1 | john | ekjalsdjalfjkldd |
| 4 | alex | akjsldfjaekallee |
| 4 | alex | jkjlkjslakjfjflj |
| 4 | alex | kajslejajejjaddd |
| 6 | bob | ekakdie33kkd93ld |
| 6 | bob | 33kd993kakakl3ll |
| 12 | paul | 3k309dki595k3lkd |
| 12 | paul | 3k399kkfkg93lk3l |
etc...
So I added in some id numbers here. These ID numbers pertain to the people's names in the tables. So you can see in the results, not every single person/name in the table is necessarily in the result (due to some WHERE condition). So the 50th distinct name in the list will always have an ID number higher than 49. The 50th person could be id 79, 234, 4954 etc...
So back to the problem. The subselect SELECT DISTINCT name FROM testTable LIMIT 50 selects the first 50 names in the table. That means that my search results will be limited to names that have ID <=50, which is too constricting. If there are certain names that don't show up in the query (due to some WHERE condition), then they are still counted as one of the 50 distinct names. So you end up with too few results.
UPDATE 2
To #trapper: This is a basic simplification of what my query looks like:
SELECT
t1.id,
t1.name,
t2.details
FROM t1
LEFT JOIN t2 ON t1.id = t2.some_id
INNER JOIN
(SELECT DISTINCT name FROM t1 ORDER BY id LIMIT 0,50) s ON s.name = t1.name
WHERE
SOME CONDITIONS
ORDER BY
t1.id,
t1.name
And my results look like this:
----------------------------------
| id | name | details |
----------------------------------
| 1 | john | ekjalsdjalfjkldd |
| 3 | alex | akjsldfjaekallee |
| 3 | alex | jkjlkjslakjfjflj |
| 4 | alex | kajslejajejjaddd |
| 6 | bob | ekakdie33kkd93ld |
| 6 | bob | 33kd993kakakl3ll |
| 12 | paul | 3k309dki595k3lkd |
| 12 | paul | 3k399kkfkg93lk3l |
...
| 37 | bill | kajslejajejjaddd |
| 37 | bill | ekakdie33kkd93ld |
| 41 | matt | 33kd993kakakl3ll |
| 50 | jake | 3k309dki595k3lkd |
| 50 | jake | 3k399kkfkg93lk3l |
----------------------------------
The results stop at id=50. There are NOT 50 distinct names in the list. There are only roughly 23 distinct names.
My MySql syntax may be rusty, but the idea is to use a query to select the top 50 distinct names, then do a self-join on name and select the name and other information from the join.
select a.name, b.something_random
from Table b
inner join (select distinct name from Table order by RAND() limit 0,50) a
on a.name = b.name
SELECT DISTINCT name FROM table LIMIT 0,50
Edited: Ahh yes I misread question first time, this should do the trick though :)
SELECT a.name, b.something_random
FROM `table` b
INNER JOIN (SELECT DISTINCT name FROM `table` ORDER BY RAND() LIMIT 0,50) a
ON a.name = b.name ORDER BY a.name
How this work is the (SELECT DISTINCT name FROMtableORDER BY RAND() LIMIT 0,50) part is what pulls out the names to include in the join. So here I am taking 50 unique names at random, but you can change this to any other selection criteria if you want.
Then you join those results back into your table. This links each of those 50 selected names back to all of the rows with a matching name for your final results. Finally ORDER BY a.name just to be sure all the rows for each name end up grouped together.
This should do it:
SELECT tA.*
FROM
testTable tA
INNER JOIN
(SELECT distinct name FROM testTable LIMIT 50) tB ON tA.name = tB.name
;