Finding a ranking from a rating field on MySQL - php

I have a MySQL table that looks like this:
id (int primary)
name (text)
rating (float)
I have a page showing rankings which looks like this:
$i = 0;
$q = mysql_query("SELECT * FROM teams ORDER BY rating DESC");
while($r = mysql_fetch_assoc($q)){
$i++;
print("$i: {$r['name']}<br>");
}
This shows teams in order of their rating, with a ranking. And it works.
Now, if I'm given the ID of a team, how do I find their ranking without running through the loop like this? A single MySQL query which returns the team's info + a numeric ranking indicating how far down the list they would be, if I had rendered the whole list.
Thanks!

To get the ranking you can do:
SELECT COUNT(*) as ranking
FROM teams t
WHERE t.rating >= (SELECT rating FROM teams WHERE id=$ID);
To get all the relevant info too, you can do:
SELECT t.*,COUNT(*) as rank
FROM teams t
JOIN teams t2 ON t.rating<=t2.rating
WHERE t.id=4;
This joins teams to itself joining on t.rating <= t2.rating, and so you get one row for every team that has a rating higher than or equal you.
The COUNT just counts how many teams have a rating higher than or equal to you.
Note that if there's a tie this will give you the lower rank. You can change the <= to a < if you want the highest.

You can also do it this way:
select * from (
select t.*, #rank := #rank + 1 as rank
from (select #rank := 0) as r, t
order by rating desc
) as t
where id = 20

Related

Sorting data from MySQL by SUM with Grouping By - not working properly

I have a problem with mysql - i'm kind new to it, byt looking to improve my skills :)
Have a code like this:
if($where > 0) $query = mysql_query("SELECT img.*, user.user as owner_name, cat.name as cat_name FROM tentego_img AS img LEFT JOIN tablicacms_users AS user ON user.id = img.owner LEFT JOIN tentego_img_cat AS cat ON cat.id = img.cat WHERE img.`is_waiting` LIKE ".$where.$cat." INNER JOIN tentego_img_vote ON tentego_img.id = tentego_img_vote.object_id GROUP BY tentego_img_vote.object_id ORDER BY SUM ( (CASE WHEN tentego_img_vote.vote = '0' THEN '-1' ELSE '1' END) ) DESC LIMIT ".$page.",".$objPerPage);
I need to make sorting by number of votes, sorted descending.
Still it makes results sorted by it own way.
In table I have rows:
ID - vote id for table purpose
object_id- id of object joined with another table to show results.
User ID - user id
Vote - where values are 0 for dislike and 1 for like (so -1 for 0, and +1 for 1)
So, as I understand i need to sum up all records for each of unique object_id, then sort by sum of vote values of each.
This code worked before my script provider decide to upgrade it, so right now i dont know how to fix it :(

How do i optimise a mysql query that updates rank order in nested loop?

There is a score link table that holds a list of item_ids, the category_ids they are assigned to, and a score.
What I am trying to do is
For each category, grab all the items assigned to it, ordered by highest score first.
This then defines that items RANK ORDER in that category.
I then want to store that items RANK in the same table against the matching item_id and category_id.
This works great with the PHP MySQL code i have, but unfortunately there are 10000 categories and so takes about 30mins before timing out.
Here is an example of what im doing:
SELECT category_id FROM tbl_categories /* - this just grabs a list of categorys to loop through */
Loop through each row returned {
SELECT item_id, score
FROM tbl_scores
WHERE category_id = CATEGORY_ID ORDER BY score DESC
MYCOUNT = 0
Loop through each results, incrementing the count and storing in another tables {
MYCOUNT = MYCOUNT + 1
UPDATE tbl_scores
SET rank = MYCOUNT
WHERE item_id = ITEMID AND category_id = CATEGORYID
}
}
Try this query to get the rank for each product (using session variables, see more info here MySql - How get value in previous row and value in next row?)
SELECT
s.categoryid ,
s.itemid,
s.score,
#rowid := IF (#prev_categroy = s.categoryid, #rowid+1, 0),
#prev_categroy := s.categoryid AS curr
FROM scores s, (SELECT #rowid:=0, #prev_categroy := NULL) AS init
ORDER BY s.categoryid, s.score DESC
I would store the results of this (ranks) in a temporary table and then update in another statement with a simple join by item_id, supposing the size of the table is not huge.

Random allocation until all options are used

I have a table of available teams teams, with 24 different options.
I have another table entries, where each row is an allocation of one team to a user.
When an entry is created, a random team that has not been picked is allocated. However, if all the teams have been allocated (this can happen multiple times), only teams not yet allocated in this round of allocation are available.
For example, if my teams are A, B, C and D:
If there is an entry for A in entries, only B, C and D are available
If A, B, C and D have been picked, they are all available again
IF A has 3 entries, B has 3 entries, C has 2 entries and D has 2 entries, only C and D are available, until they all have the same number of entries
My code for this is convoluted:
//Make array of teams
for($i=1;$i<=24;$i++) $team[$i] = 1;
//Get entries from database
$stmt = $dbh->prepare("SELECT `team` FROM `entries`");
$stmt->execute();
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
//Create array of available teams
$numRows = $stmt->rowCount();
while($numRows >= 24) {
for($i=1;$i<=24;$i++) {
$team[$i] = $team[$i]+1;
}
$numRows = $numRows - 24;
}
//Remove entries for teams in array
foreach($rows as $row) $team[$row["team"]] = $team[$row["team"]]-1;
foreach($team as $i => $v) if($v > 0) $available[] = $i;
There must be a more straightforward method to accomplish this; how can this be done?
The following gives you the number of assignments for each team:
SELECT team, COUNT(*) FROM entries GROUP BY team;
This gives you the minimum count for any team:
SELECT MIN(count) FROM (
SELECT COUNT(*) as count FROM entries GROUP BY team
)
To get the teams with the minimum count - those being available - but those two queries together into one:
SELECT teamcounts.team
FROM
(SELECT team, COUNT(*) as num FROM entries GROUP BY team) as teamcounts
WHERE
teamcounts.num = (
SELECT MIN(num) FROM (
SELECT COUNT(*) as num FROM entries GROUP BY team
) as tcounts
)
To get also those teams not yet included in entries we have to use the team table as well, removing all teams not currently available for selection:
SELECT teams.name
FROM teams
WHERE teams.name NOT IN (
SELECT teamcounts.team
FROM
(SELECT team, COUNT(*) as num FROM entries GROUP BY team) as teamcounts
WHERE
teamcounts.num != (
SELECT MIN(num) FROM (
SELECT COUNT(*) as num FROM entries GROUP BY team
) as tcounts
)
)
I haven't found a solution that works solely in SQL, however I've created the following query:
SELECT `id`, `num_selected` FROM
(SELECT `id`, SUM(is_selected) AS `num_selected` FROM
(SELECT t.`id`, CASE WHEN e.`team` IS NULL THEN 0 ELSE 1 END AS is_selected FROM `entries` e RIGHT JOIN `teams` t ON t.`id` = e.`team`)
AS `table1`
GROUP BY `id`)
AS `table2` GROUP BY `id` ORDER BY `num_selected` ASC, `id` ASC
This includes all the team rows that have NO entries yet, and the result is a table that has every team in one column, and alongside them is the number of selections.
Then, in PHP, I simply take the lowest value of selections (this will be the first row, as I've ordered by num_selected ASC) and only use other rows with that value as possible options:
$baseNum = $rows[0]["num_selected"];
foreach($rows as $row){
if($row["num_selected"]===$baseNum) $availableTeams[] = $row["id"];
}
However, ideally I'd have a solution that takes place solely in the SQL query!

how to get the position of sorted rows using mysql and php

I have a table which stores high-scores, along with player ids. I want to be able to extract a record by a players id, and then get the rank, or position of their score in the table. Means, Basically I want to be able to say "you are in Nth" position, purely based on the players score against all other scores. For Example: if i am at 46th position then to me the position message will be like you are at 46th position out of total scores. Can anyone show me small example?
There are two ways of doing it:
Method 1:
SET #i = 0;
SELECT * FROM
scores s1 INNER JOIN (SELECT *, #i := #i + 1 AS rank FROM scores ORDER BY score DESC) AS s2 USING (id);
Method 2:
SELECT *, (SELECT COUNT(1) AS num FROM scores WHERE scores.score > s1.score) + 1 AS rank FROM scores AS s1
ORDER BY rank asc
This will provide duplicate rank values when there are duplicates:
SELECT t.playerid,
t.highscore,
(SELECT COUNT(*)
FROM TABLE x
WHERE x.playerid = t.playerid
AND x.highscore >= t.highscore) AS rank
FROM TABLE t
WHERE t.playerid = ?
IE: If three players have the same score for second place, they'll all have a rank value of two.
This will give a distinct value - three players tied for second place, only one will be ranked as 2nd:
SELECT x.playerid,
x.highscore,
x.rank
FROM (SELECT t.playerid,
t.highscore,
#rownum := #rownum + 1 AS rank
FROM TABLE t
JOIN (SELECT #rownum := 0) r
ORDER BY t.highscore DESC) x
WHERE x.playerid = ?
Here is an example.
You want to store the user's ID when they log in, like so...
$_SESSION['username'] = $usernamefromdb;
$_SESSION['id'] = $userid;
And then you want to open a session on every page on yoru website that you will be pulling dynamic information depending on the $_SESSION['id']
session_start();
Then find the row of data in the datebase according to the userID
$userid = $_SESSION['id'];
$rank_query = "SELECT * FROM table_name WHERE id='$userid'";
$rank_result = mysqli_query($cxn, $rank_query) or die("Couldn't execute query.");
$row = mysqli_fetch_assoc($rank_result)
Then using PHP, declare the nth postiion as a variable. And pull the total amount of rows from the DB
$rank = $row['rank'];
$all = $numrows = mysqli_num_rows($result);
echo out the players rank.
echo $rank . "out of" . $all;

Mysql Limit column value repetition N times

I have two tables
Customer (idCustomer, ecc.. ecc..)
Comment (idCustomer, idComment, ecc.. ecc..)
obviously the two table are joined together, for example
SELECT * FROM Comment AS co
JOIN Customer AS cu ON cu.idCustomer = co.idCustomer
With this I select all comment from that table associated with is Customer, but now I wanna limit the number of Comment by 2 max Comment per Customer.
The first thing I see is to use GROUP BY cu.idCustomer but it limits only 1 Comment per Customer, but I wanna 2 Comment per Customer.
How can I achieve that?
One option in MySQL is server-side variables. For example:
set #num := 0, #customer := -1;
select *
from (
select idCustomer
, commentText
, #num := if(#customer = idCustomer, #num + 1, 1)
as row_number
, #customer := idCustomer
from Comments
order by
idCustomer, PostDate desc
) as co
join Customer cu
on co.idCustomer = cu.idCustomer
where co.row_number <= 2
This version doesn't require the SET operation:
select *
from (select idCustomer
, commentText
, #num := if(#customer = idCustomer, #num + 1, 1) as row_number
, #customer = idCustomer
from Comments
JOIN(SELECT #num := 0, #customer := 1) r
order by idCustomer, PostDate desc) as co
join Customer cu on co.idCustomer = cu.idCustomer
where co.row_number <= 2
SELECT * FROM Comments AS cm1
LEFT JOIN Comments AS cm2 ON cm1.idCustomer = cm2.idCustomer
LEFT JOIN Customer AS cu ON cm1.idCustomer = cu.idCustomer
WHERE cm1.idComment != cm2.idComment
GROUP BY cm1.idCustomer
However, if you are going to change the number of comments it's better to use Andomar's solution.
There is no need to use cursor, which is very slow. See my answer to Complicated SQL Query About Joining And Limitting. DENSE_RANK will do the trick without all cursor intricacies.
If you are using a scripting language such as PHP to process the results, you could limit the number of results shown per customer after running the query. Set up an array to hold all the results, set up another array to hold the number of results per customer and stop adding the query results to the result set after the count exceeds your limit like so:
$RESULTS = array();
$COUNTS = array();
$limit = 2;
$query = "SELECT customer_id, customer_name, customer_comment FROM customers ORDER BY RAND()";
$request = mysql_query($query);
while ($ROW = mysql_fetch_assoc($request))
{
$c = $ROW['customer_id'];
$n = $COUNTS[$c];
if ($n<$limit)
{
$RESULTS[] = $ROW;
$COUNTS[$c]++;
}
}
This guarantees only two comments per customer will be shown pulled randomly or however you want, the rest gets thrown out. Granted you are pulling ALL the results but this is (probably) faster than doing a complex join.

Categories