I have a table with all players results:
id |result| user_id
------------------------
1 | 130 | 5C382072
2 | 145 | 5C382072
3 | 130 | 8QHDTz7w
4 | 166 | 6155B6D0
5 | 100 | DFSA3444
Smaller result is better. I need to make query for leaderboard.
Each player must appear once in leaderboard with his best result. If 2 players have equal results, the one with smaller id should appear first.
So I'm expecting this output:
id |result| user_id
------------------------
5 | 100 | DFSA3444
1 | 130 | 5C382072
3 | 130 | 8QHDTz7w
4 | 166 | 6155B6D0
I can't get desired result, cause grouping by user_id goes before ordering it by result, id.
My code:
SELECT id, MIN(result), user_id
FROM results
GROUP BY user_id
ORDER BY result, id
It output something close to desired result, but id field is not connected to row with smallest user result, it can be any id from group with the same user_id. Because of that ordering by id not work at all.
EDIT:
What I didn't mention before is that I need to handle situations when user have identical results.
I came up with two solutions that I don't like. :)
1) A bit slow and ugly:
SELECT t1.*
FROM (SELECT * FROM results WHERE results_status=1) t1
LEFT JOIN (SELECT * FROM results WHERE results_status=1) t2
ON (t1.user_id = t2.user_id AND (t1.result > t2.result OR (t1.result = t2.result AND t1.id > t2.id)))
WHERE t2.id IS NULL
ORDER BY result, id
2) Ten times slower but more clear:
SELECT *
FROM results t1
WHERE id = (
SELECT id
FROM results
WHERE user_id = t1.user_id AND results_status=1
ORDER BY result, id
LIMIT 1
)
ORDER BY result, id
I'm stuck. :(
This should get you close. Avoid MySQL's lenient (errant) GROUP BY syntax, which lets you form a GROUP BY clause without naming unaggregated columns from the SELECT list. Use standard SQL's GROUP BY syntax instead.
select t.user_id, m.min_result, min(t.id) id
from results t
inner join (select user_id, min(result) min_result
from results
group by user_id) m
on t.user_id = m.user_id
and t.result = m.min_result
group by t.user_id, m.min_result
Edit: I think you need a subquery:
SELECT a.id, a.result, a.user_id FROM results a
WHERE a.user_id, a.result IN (SELECT b.user_id, MIN(b.result) FROM results b
GROUP BY b.user_id)
ORDER BY a.user_id
This will return an undefined id if the same user had the same score more than once, but will order the users correctly and will match id to the correct user_id.
Related
Two tables are used in this query, and all that matters in the result is the number of users which have or haven't posted any comments so far. The table user of course has the column id, which is the foreign key in the table comment, identified by the column user_id.
The first super-simple query groups users by whether or not they have any comments so far. It outputs two rows (a row with the user count who have comments, and a row with the user count who have no comments), with two columns (number of users, and whether or not they have posted any comments).
SELECT
COUNT(id) AS user_count,
IF( id IN ( SELECT user_id FROM `comment` ), 1, 0) AS has_comment
FROM `user`
GROUP BY has_comment
An example of how the output would look like here:
+------------+-------------+
| user_count | has_comment |
+------------+-------------+
| 150 | 0 |
| 140 | 1 |
+------------+-------------+
Now here comes my question. I want slightly more information here, by grouping these users into 3 groups instead:
Users that have posted no comments
Users that have posted fewer than 10 comments
Users that have posted 10 or more comments
And the best query that I know how to write for this purpose is as follows, which works, but unfortunately runs 4 subqueries and has 2 derived tables:
SELECT
COUNT(id) AS user_count,
CASE
WHEN id IN ( SELECT user_id FROM ( SELECT COUNT(user_id) AS comment_count, user_id FROM `comment` GROUP BY user_id HAVING comment_count >= 10 ) AS a) THEN '10 or more'
WHEN id IN ( SELECT user_id FROM ( SELECT COUNT(user_id) AS comment_count, user_id FROM `comment` GROUP BY user_id HAVING comment_count < 10 ) AS b) THEN 'less than 10'
ELSE 'none'
END AS has_comment
FROM `user`
GROUP BY has_comment
An example of the output here would be something like:
+------------+-------------+
| user_count | has_comment |
+------------+-------------+
| 150 | none |
| 130 | less than 10|
| 100 | 10 or more |
+------------+-------------+
This second query; can it be written more simply and efficiently, and still produce the same kind of result? (potentially maybe even be expanded into more of these kinds of "groups")
You can use two levels of aggregation:
select
count(*) no_users,
case
when no_comments = 0 then 'none'
when no_comments < 10 then 'less than 10'
else '10 or more'
end has_comment
from (
select
u.id,
(select count(*) from comments c where c.user_id = u.id) no_comments
from users u
) t
group by has_comment
order by no_comments
The subquery counts how many comments each user has (you could also express this with a left join and aggregation); then, the outer query classifies and count the users per number of comments.
I have tried quite a lot of solutions and I decided to post it here to try and find a solution. Any little help is welcome (so I can learn too).
I have a table formed by ArticleID, UserID, and Votes (1/-1).
I want to select the ArticleID that contains a certain UserID and which SUM of Votes is equal to 1.
So far I arrived to:
SELECT catch.ID, votes.postid, catch.text, votes.userid, votes.value, catch.name FROM catch INNER JOIN votes ON catch.ID=votes.postid AND votes.userid=:iduser AND votes.value='1' ORDER BY ID DESC LIMIT 100
but this gives me an erroneous result, as it doesn't consider articles that have votes 1 and -1 (which SUM should be 0).
Thanks!
UPDATE
ID + Value + userid
1 | 1 | 54
1 | -1 | 54
3 | 1 | 54
7 | 1 | 56
7 | -1 | 56
Given the above table, and selecting just the user '54' the wanted result should be ID 3.
Is this what you want?
SELECT c.ID, v.postid, c.text, v.userid, v.value, c.name
FROM catch c INNER JOIN
votes v
ON c.ID = v.postid AND v.userid = :iduser
GROUP BY c.ID
HAVING SUM(v.value) = 1;
This is what you describe but it is a bit different from your query.
Try like this, but for :iduser set what particular id of user
SELECT catch.ID,
votes.postid,
catch.text,
votes.userid,
SUM(votes.value),
catch.name
FROM catch
LEFT JOIN votes ON catch.ID=votes.postid
WHERE votes.userid = :iduser and votes.value='1'
ORDER BY ID DESC
LIMIT 100
So I want to select * from "board_b" the thread that has the most replies. My problem is that the replies are actually in the same table. Take a look at this:
+---+-----------+---------+
|ID | name | replyto |
+---+-----------+---------+
| 1 | newthread | |
| 2 | reply | 1 |
+---+-----------+---------+
(NOTE: the name column is not set to those, it is just to demonstrate) As you can see, 1 is a new thread, and 2 is a reply to 1. Now I have a table full of these, and the table has more columns (text, timestamp, etc...) but the general idea is like the one above.
The thing I want to achieve is select all threads, and sort them by most replies (and also limit by 0, 20). I've tried looking in to joining tables but it get's too complicated for me to understand, so a sample code would be great.
Something like this will do it:
SELECT board.id, board.name, COUNT(reply.id)
FROM board_b board INNER JOIN board_b reply ON board.id = reply.replyto
GROUP BY board.id, board.name
ORDER BY COUNT(reply.id) desc
LIMIT 20
You want to use group by:
select replyto as thread, count(*) as cnt
from board_b
group by replyto
order by cnt desc
limit 0, 20;
select c.replyto, c.replycount
from
(
select a.replyto as replyto, count(*) as replycount
from board_b a
inner join (
select id, name, replyto
from board_b
where replyto is null
) b
on b.id = a.replyto
group by a.replyto
) c
where c.replycount between 0 and 20
order by c.replycount desc
I have the following SQL query which queries my tickets, ticketThreads, users and threadStatus tables:
SELECT tickets.threadId, ticketThreads.threadSubject, tickets.ticketCreatedDate, ticketThreads.threadCreatedDate, threadStatus.threadStatus, users.name
FROM
tickets
INNER JOIN
ticketThreads
ON
tickets.threadId = ticketThreads.threadId
INNER JOIN
threadStatus
ON
ticketThreads.threadStatus = threadStatus.id
INNER JOIN
users
ON
users.id = ticketThreads.threadUserId
WHERE
tickets.ticketId = ticketThreads.lastMessage
AND
ticketThreads.threadStatus != 3
ORDER BY
tickets.ticketCreatedDate
DESC
The abridged version of what this returns is:
threadId |
----------
1 |
2 |
This works fine, and is what I expect, however to clean up the code and database slightly I need to remove the ticketThreads.lastMessage column.
If I remove the line WHERE tickets.ticketId = ticketThreads.lastMessage then this is an abridged version of what is returned:
threadId |
----------
1 |
2 |
1 |
What I need to do then is edit the query above to enable me to select the highest unique value for each threadId value in the tickets database.
I know about MAX() and GROUP BY but can't figure how to get them into my query above.
The relevant parts of the tables are shown below:
tickets
ticketId | ticketUserId | threadId
-------------------------------
1 | 1 | 1
2 | 1 | 2
3 | 1 | 1
ticketThreads
threadId | lastMessage | threadStatus
-------------------------------
1 | 3 | 4
2 | 2 | 1
I hope all the above is clear and makes sense
So you need the ticket with the highest id per each thread? Your problem is actually very easy variant of greatest record per group problem. No need for any subqueries. Basicaly you have two options, which both should perform much better than your query, the second be faster (please post the actual durations in your db!):
1. Standard compliant query, but slower:
SELECT t1.threadId, ticketThreads.threadSubject, t1.ticketCreatedDate,
ticketThreads.threadCreatedDate, threadStatus.threadStatus, users.name
FROM tickets as t1
LEFT JOIN tickets as t2
ON t1.threadId = t2.threadId AND t1.ticketId < t2.ticketId
JOIN ticketThreads ON t1.threadId = ticketThreads.threadId
JOIN threadStatus ON ticketThreads.threadStatus = threadStatus.id
JOIN users ON users.id = ticketThreads.threadUserId
WHERE t2.threadId is NULL
AND ticketThreads.threadStatus != 3
ORDER BY t1.ticketCreatedDate DESC
This one joins the tickets table two times, which can make it a bit slower for big tables.
2. Faster, but uses MySQL extension to standard SQL:
set #prev_thread := NULL;
SELECT t.threadId, ticketThreads.threadSubject, t.ticketCreatedDate,
ticketThreads.threadCreatedDate, threadStatus.threadStatus, users.name
FROM tickets as t
JOIN ticketThreads ON t.threadId = ticketThreads.threadId
JOIN threadStatus ON ticketThreads.threadStatus = threadStatus.id
JOIN users ON users.id = ticketThreads.threadUserId
WHERE ticketThreads.threadStatus != 3
AND IF(IFNULL(#prev_thread, -1) = #prev_thread := t.threadId, 0, 1)
ORDER BY t.threadId, t.ticketId DESC,
t.ticketCreatedDate DESC
Here, we perform one pass scan on ordered joined data, using auxiliary mysql variable #prev_thread to filter only the first (in the given order) ticket for each thread (the one with highest ticketId).
I have the following database
id | user | urgency | problem | solved
The information in there has different users, but these users all have multiple entries
1 | marco | 0 | MySQL problem | n
2 | marco | 0 | Email problem | n
3 | eddy | 0 | Email problem | n
4 | eddy | 1 | MTV doesn't work | n
5 | frank | 0 | out of coffee | y
What I want to do is this: Normally I would check everybody's oldest problem first. I use this query to get the ID's of the oldest problem.
select min(id) from db group by user
this gives me a list of the oldest problem ID's. But I want people to be able to make a certain problem more urgent. I want the ID with the highest urgency for each user, or ID of the problem with the highest urgency
Getting the max(urgency) won't give the ID of the problem, it will give me the max urgency.
To be clear: I want to get this as a result
row | id
0 | 1
1 | 4
The last entry should be in the results since it's solved
Select ...
From SomeTable As T
Join (
Select T1.User, Min( T1.Id ) As Id
From SomeTable As T1
Join (
Select T2.User, Max( T2.Urgency ) As Urgency
From SomeTable As T2
Where T2.Solved = 'n'
Group By T2.User
) As MaxUrgency
On MaxUrgency.User = T1.User
And MaxUrgency.Urgency = T1.Urgency
Where T1.Solved = 'n'
Group By T1.User
) As Z
On Z.User = T.User
And Z.Id = T.Id
There are lots of esoteric ways to do this, but here's one of the clearer ones.
First build a query go get your min id and max urgency:
SELECT
user,
MIN(id) AS min_id,
MAX(urgency) AS max_urgency
FROM
db
GROUP BY
user
Then incorporate that as a logical table into
a larger query for your answers:
SELECT
user,
min_id,
max_urgency,
( SELECT MIN(id) FROM db
WHERE user = a.user
AND urgency = a.max_urgency
) AS max_urgency_min_id
FROM
(
SELECT
user,
MIN(id) AS min_id,
MAX(urgency) AS max_urgency
FROM
db
GROUP BY
user
) AS a
Given the obvious indexes, this should be pretty efficient.
The following will get you exactly one row back -- the most urgent, probably oldest problem in your table.
select id from my_table where id = (
select min(id) from my_table where urgency = (
select max(urgency) from my_table
)
)
I was about to suggest adding a create_date column to your table so that you could get the oldest problem first for those problems of the same urgency level. But I'm now assuming you're using the lowest ID for that purpose.
But now I see you wanted a list of them. For that, you'd sort the results by ID:
select id from my_table where urgency = (
select max(urgency) from my_table
) order by id;
[Edit: Left out the order by!]
I forget, honestly, how to get the row number. Someone on the interwebs suggests something like this, but no idea if it works:
select #rownum:=#rownum+1 ‘row', id from my_table where ...