I know this is a popular topic, but I still haven't found quite what I'm looking for. I'd like to query one table
BOOKS_READ
id
user_id
book_id
to format a leaderboard of users who have listed the most books as having been read. When a user reads a book, a record matching the book id and the user id gets logged into the books_read table.
Is it possible to rank the results of this query, starting at 1, and with consideration of ties?
SELECT user_id, COUNT(*) AS book_count
FROM books_read
GROUP BY user_id
ORDER BY book_count DESC LIMIT 10
In the event of a tie, I would like to list an '=' sign in the results.
For example,
rank user_id book_count
=1 30 121
=1 17 121
2 101 119
=3 11 104
=3 91 104
Many thanks for any help! I don't mind using PHP to handle some of it, but I'm very interested in learning straight SQL solutions to these kinds of things :-)
SELECT GROUP_CONCAT(user_id, book_count
FROM (
SELECT user_id, COUNT(*) AS book_count
FROM books_read
GROUP BY user_id
ORDER BY book_count DESC
) AS T1
GROUP BY book_count
ORDER BY book_count
Giving you
user_id book_count
30,17 121
101 119
11,91 104
Which you can then use PHP to parse for ties.
<?php
$rank = 1;
while ($row = mysql_fetch_assoc($result)) {
$users = explode(',', $row['user_id'];
foreach ($users as $user) {
echo 'Rank: ' . $rank . ' ' . $user . "\n;
}
$rank++;
}
?>
What you want to do is count the number of people who have a better score than the record you're interested in, and add 1.
So, if you're the best player, zero people have a better score, so 0 + 1 = #1 ranking.
If five people have a better score, regardless of how many of them tied with each other, you're still 5 + 1 = 6.
The problem is that this is kind of an expensive query to do for every row you display. So you probably want to do it for the first row in your result set and then add one for everyone after that in your presentation layer (PHP). Be sure to take ties into consideration as you do this.
And the edge condition is that you don't know if the first row in your result set is tied with people ahead of "him". So you need to know how many people have the same score as the first person in your result set. (If you're starting at the top, this isn't a problem.)
Related
I'm trying create a poll module within codeigniter using hmvc
I'm trying to return the count as percentages against the amount of user_id's that have voted into the candidate_id
I'm using a so called perfectcontroller and perfectmodel
where I can return the count on the number of rows each candidate has a user_id
but I have seven candidate_id's so getting the percentages is a little tricky
but its also tricky trying to get the count to match up against the id its counting
I do not know if this is the right way to go about it or if I should be doing it another way any suggestions would help
thanks
<?php
function get_result(){
$query = $this->get_where_custom('candidate_id',$candidate_id);
$num_rows = $query->num_rows();
//returns the amount of user votes
foreach($query->result() as $row){
$candidate_id = $row->candidate_id;
}
}
?>
No matter if you have 7 or 77 candidates. Votes are counting. So if you have only one vote in DB, it means 100% for one candidate and 0% for rest of them. I don't know what do you consider by perfectcontroller and perfectmodel. But your table could be like
election_id | voter_id | candidate_id
your query would be like
$this->db->query("SELECT `e`.`candidate_id`, COUNT(1) AS `total`, COUNT(1) / `t`.`cnt` * 100 AS `percentage` FROM `elections` `e` CROSS JOIN (SELECT COUNT(1) AS `cnt` FROM `elections`) `t` GROUP BY `e`.`candidate_id`;");
I've a database with 8.000.000 rows, with 2 columns 'id' and 'score', i know also the exact number of users with score=0 (that is near 4.000.000).
I've a ranking board in my webpage with the query:
"SELECT id, score FROM table ORDER BY score DESC LIMIT ".$num_rank.", 25"
$num_rank defines which page show, and it's 25 users per page.
The query works, and it show what it has to show but the problem is that the query is really slow.
The question is the next query may be faster?:
$num_users_score_0 = 4000123
if(score==0){
$num_rank=$num_rank-$num_users_score_0
query=**"SELECT id, score FROM table WHERE score = 0 ORDER BY score DESC LIMIT ".$num_rank.", 25"**
}else{
query=**"SELECT id, score FROM table WHERE score > 0 ORDER BY score DESC LIMIT ".$num_rank.", 25"**
}
All suggestions are appreciatted!
Thanks!!!
Yes, your queries will be faster, with one improvement to the first: the `order by is unnecessary:
query="SELECT id, score FROM table WHERE score = 0 LIMIT ".$num_rank.", 25"
ALso, this will be faster with an index on table(score, id). In fact, with this index, you may be able to use your original query.
I was trying to create a single SQL query to return what I need, instead of creating 2 querys, but I need to use the result of one of the querys as the offset of the other one.
My table has user answers with scores, each user may have multiple answers in that table. And I want to calculate the middle point of the table of ordered scores.
Example:
User answers:
User 1 - 5 points
User 1 - 15 points
User 2 - 8 points
User 3 - 12 points
Ranking Table:
User 1 - 20 points
User 3 - 12 points < Middle point
User 2 - 8 points
Solution:
The first query calculates the middle point:
SELECT CEILING(count(Distinct(id_user)) / 2) as position
FROM us_user_response
where id_round=1
Query result:
position : 2
This second query creates the ordered ranking table:
SELECT sum(points) as score
FROM us_user_response
where id_round=1
GROUP BY id_user
Order by score DESC
Now I want to create one big query that returns the score of the middle user, I just need to use the first query result as offset of the second query:
SELECT sum(points) as score
FROM us_user_response
where id_round=1
GROUP BY id_user
Order by score DESC LIMIT 1
OFFSET (SELECT CEILING(count(Distinct(id_user)) / 2) as position
FROM us_user_response where id_round=1)
Of course, this doesn't work, is there any way to use a result as offset?
EDIT:
The queries work nice! My question is if there is any way to use the result of a query as the offset of another. So I could accomplish this in one single query.
Try something like this:
SET #line_id = 0;
SET #line_offset = (
SELECT CEILING(count(Distinct(id_user)) / 2) as position
FROM us_user_response
WHERE id_round = 1
);
SELECT sum(points) as score,
IF((#line_id := #line_id + 1) = #line_offset, 1, 0) AS useit
FROM us_user_response
WHERE id_round = 1
GROUP BY id_user
HAVING useit = 1
ORDER BY score;
Select 3 rows from table1
Get a specific column data out of each row.
Then use that each column data obtained , to make a query again to get data from table2.
Store the data obtained in step 4 into a variable for each row.
Then put them in json array (table 1 , 3 rows + table 2's data(each of them).
I am building a rank table, it displays top 3 users with their rank name.
For example:
User1 has 2000 points , user 2 has 4000points , user 3 has 10k points , so the top 3 user is :
user 3 > user 2 > user 1
So , i want the php to go to 'users' table and get the top 3 members using this:
$query = mysql_query("SELECT * FROM users ORDER BY pts DESC LIMIT 3");
$rows = array();
while($r = mysql_fetch_assoc($query)) {
$rows[] = $r;
}
Table structure for 'user':
1.username(varchar)
2.pts(int)
After the rows are put into an array , how can i get 'points' for each of the row in that array.
Then go to 'rank' table to get their ranknames.
Table structure for 'rank':
1.rank(varchar)
2.pts(int)
Inside rank table there is 'pts' to let php choose compare which rank the user is at based on the points from each row of the array.
Normally i would use this if its only for 1 user , but for multiple users , im not sure:
$result = mysql_query("SELECT * FROM rank WHERE pts <= '$upts' ORDER BY pts DESC LIMIT 1")
or die(mysql_error());
Then after getting the rank for the top 3 users , php will now add the ranks to each of the user(row) in that array(of course , add it to the rank owner, not just simply place it in).
Then JSON encode it out.
How can i do this?
I am not sure if this is what you want. That is combine the two query into one query. Please take a look at http://sqlfiddle.com/#!2/ad419/8
SELECT user.username,user.pts,rank.rank
FROM user LEFT JOIN rank
ON user.pts <=rank.pts group by user.id
UPDATED:
For extracting top 3, could do as below;
SELECT user.username,user.pts,rank.rank
FROM user LEFT JOIN rank
ON user.pts <=rank.pts
GROUP BY user.id
ORDER BY pts DESC LIMIT 3
If i understand correctly, you need to get values from Rank and Users tables. In order to do that in just one query You need to add FK (Foreign Key) to the Rank table that points to a specific user in the Users table.
So you need to add userId to the Rank table and then you can run:
SELECT r.rank, u.points from users u,rank r where u.userId = r.userId
This is roughly what you need.
Not quite the answer to your exact question, but this might be of use to you: How to get rank using mysql query. And may even mean that you don't require a rank table. If this doesn't help, I'll check back later.
Use this query
$query = "SELECT
u.pts,
r.rank
FROM users as u
left join ranks as r
on r.pts = u .pts
ORDER BY pts DESC
LIMIT 3";
This will bring what you required without putting into an array
$rec = mysql_query($query);
$results = arrau();
while($row = mysql_fetch_row($rec)){
$results[] = $row;
}
echo json_encode($results);
It looks like what you're trying to do is retrieve the rank with the highest point requirement that the user actual meets, which isn't quite what everyone else is giving here. Fortunately it is easily possible to do this in a single query with a nice little trick:
SELECT
user.username,
SUBSTRING_INDEX(GROUP_CONCAT(rank.rank ORDER BY pts DESC),",",1) AS `rank`
FROM user
LEFT JOIN rank ON user.pts >= rank.pts
GROUP BY user.id
ORDER BY pts DESC
LIMIT 3
Basically what the second bit is doing is generating a list of all the ranks the user has achieved, ordering them by descending order of points and then selecting the first one.
If any of your rank names have commas in then there's another little tweak we need to add on, but I wouldn't have thought they would so I've left it out to keep things simple.
My case is, when someone is posting to my news web data with 600k records,
they can create the same title which someone just posted in the last few hours with same subject.
data: sid|title|desc|timestamp|userid
Usually I use:
select sid from ".$prefix."_stories WHERE title = '".$subject."' limit 0,1
It searches all data for $subject then notice poster if have they have the same title.
I need a query which searches in the last 100 records.
I tried the following, but this did not work:
select sid from ".$prefix."_stories WHERE title = '".$subject."' and sid > MAX(sid)-100 limit 0,1
You can do this with a subquery that returns the last 100 sid. Note, I have omitted the PHP concatenations for brevity here.
SELECT
last100.sid
FROM
(
SELECT
sid,
title
FROM $prefix._stories
ORDER BY sid DESC LIMIT 100
) last100
WHERE
last100.title = '$subject'
Assuming that you have a primary key, incrementing by 1 with every record (which will put newest at the bottom), just do this:
SELECT sid, title
FROM ".$prefix."_stories
ORDER BY sid DESC
LIMIT 100
This will select the last 100 records
Good point by Michael in my comments... Edit to code and further comment:
Removing the where clause, this will select the last 100 records... Run a loop to check to see if the title or summary already exists:
while ($row = $result) {
if $subject = $row['title'] {
$duplicate = TRUE;
endwhile;
}
}
Just compare the resulting data. Thanks Michael
Sort by sid (descending, so newest entries come first) and limit results to 100:
$sql = "select t.sid from (select sid, title from ".$prefix."_stories ORDER BY sid DESC LIMIT 0,100) t WHERE title = '".$subject."';"
Be sure to chek $subject to avoid SQL Injection (better: Use prepared statements / PDO).