mysql ranking for a single user - php

Im using a suggestion from Daniel Vassallo here to rank entries in my mysql table.
The suggestion doesn't deal with ties and thats the way I want it as a newer entry does not get a higher rank than an older entry with the same score on my scoreboard that way and it works for my needs.
My problem is that I want to be able to use this type of ranking to get the ranking for a single user. So from the output of this query I would like to define a name so that the script returns the rank, name and score of only that user.
I have tried a lot of different methods and as some of them deal with ties the results for a single user end up different from what is displayed in the results of the code below.
Your help would be greatly appreciated....going grey over this!
this is my current code:
it currently outputs:
rank name score
me 1111
me 1111
you 1110
<?php
include("common.php");
$link=dbConnect();
$limit = safe($_POST['limit']);
$query = "SELECT name, score, #curRank := #curRank + 1 AS rank
FROM $dbName . `scores`, (
SELECT #curRank := 0
) q
ORDER BY score DESC LIMIT $limit";
$result = mysql_query($query);
$my_err = mysql_error();
if($result === false || $my_err != '')
{
echo "";
}
$num_results = mysql_num_rows($result);
for($i = 0; $i < $num_results; $i++)
{
$row = mysql_fetch_array($result);
echo $row[rank] . " ". $row['name'] . " - " . $row['score'] . "\n";
}
?>
UPDATE
To clarify on ties; the original script will always increment regardless of ties this is how I want it to be because I don't want it so ties are ranked the same (no joint places) and it just so happens the script will favour the first person to achieve the score so that a new player can't knock him/her off the top spot with the same score, they have to beat it.
I know this is deprecated as I have seen in allot of similar posts but I'm just trying to get the skeleton built before I add the meat to the bones.
As kindly suggested by Spencer7593 I have tried the following code without much luck so far.
<?php
include("common.php");
$link=dbConnect();
$limit = safe($_POST['limit']);
$query = "SELECT name, score, #curRank := #curRank + 1 AS rank
FROM $dbName . `scores`, (
SELECT #curRank := 0
) q
ORDER BY score DESC LIMIT $limit";
$result = mysql_query($query);
$my_err = mysql_error();
if($result === false || $my_err != '')
{
echo "";
}
$num_results = mysql_num_rows($result);
while ($row = $result->fetch_assoc()) {
if ( $row['rank'] == 'you' )
{
// output this row because it's for the specified user
echo $row['name'];
}
else
{
continue;
}
}
?>

To get rankings for a single user extracted from the query results, you could run through the results in PHP, just like you are doing, but "skip" the output of rows that aren't for the specified user.
There's no need for a for ($i=0;i< loop. Use a "while fetch" loop. (I'm loathe to give you any example code using the deprecated mysql interface; new development should use either mysqli or PDO.)
while ($row = $result->fetch_assoc()) {
if ( $row['name'] == 'you' ) {
// output this row because it's for the specified user
echo $row['rank'];
} else {
// skip this row
}
}
You make some noise about handling "ties", but what's not clear what you actually want as output. If you want rows that have the same value for "score" have the same value for rank, just handle that in your query. If the score on the current row matches the score from the previous row, don't increment the rank. e.g.
SELECT #curRank := IF(s.score=#prev,#curRank,#curRank + 1) AS rank
, s.name
, #prev := s.score AS score
FROM $dbName . `scores` s
CROSS
JOIN (SELECT #curRank := 0, #prev := NULL) q
ORDER BY s.score DESC
LIMIT $limit
Including potentially unsafe values into the SQL text leads to SQL Injection vulnerabilities; we're going to assume that you've guaranteed the values of $dbName and $limit are safe.
If you want the query to filter out rows for a particular name, then wrap that query in parens and reference it as an inline view, e.g.
SELECT v.rank
, v.name
, v.score
FROM ( SELECT #curRank := IF(s.score=#prev,#curRank,#curRank + 1) AS rank
, s.name
, #prev := s.score AS score
FROM $dbName . `scores` s
CROSS
JOIN (SELECT #curRank := 0, #prev := NULL) q
ORDER BY s.score DESC
LIMIT $limit
) v
WHERE v.name = 'you'
ORDER BY v.rank ASC

Related

Get N redords from DB for each ID in array (Codeigniter)

Hopefully someone here can help as I've spent 2 days searching.
Basically I am trying to send an array of user ids to a CI model and retrieve the last 10 transactions for each id.
I can limit the total number of records, but I want to limit to each id instead, and cant figure out how this is done?
I have read that ROW_NUMBER() may be what I'm looking for? But I'm unsure how to turn that from SQL into something suitable for using in a CI model. Below is the code I have at the moment:
function getTrans($cData, $date){
$this->db->select('id, userId, date');
$this->db->from('trans');
$this->db->where_in('userId', $cData);
if($date != 0){
$this->db->where('date >=', $date);
}
$this->db->order_by('id','asc');
$query = $this->db->get();
if ($query->num_rows() > 0) {
return $query->result();
} else {
return false;
}
}
As you can see I'm using WHERE_IN to cycle the array of ids, but is there a way to add a limit to each id called instead of just a limit on the whole amount?
I did try adding a loop in the model but it would just keep throwing me error 500s (I think its to do with the way the query is generated??)
Any help is much appreciated
You can use $this->db->query() with the following query:
$query = $this->db->query("
SELECT *
FROM
(
SELECT
trans.*,
IF(#sameClass = userId, #rn := #rn + 1,
IF(#sameClass := userId, #rn := 1, #rn := 1)
) AS rank
FROM trans
CROSS JOIN (SELECT #sameClass := 0, #rn := 1 ) AS var
WHERE userId IN (" . implode(', ', $cData) . ")
AND date >= {$date}
ORDER BY userId, id
) AS t
WHERE t.rank <= 10
ORDER BY t.userId, t.rank
");

Using Limit in SQL according to row_count

I want to fetch number of rows with highest number of multiple of 20, Like if my table have 148 rows then limit should be 140 leaving the latest 8 entry behind, or if my table have 170 rows then limit will be 160. What will be the query in this case.
$conn = mysqli_connect($server_name, $mysql_username, $mysql_password,
$db_name);
if($conn === false){
die("ERROR: Could not connect. " . mysqli_connect_error());
}
$number= $_POST['number'];
$sql1 = "SELECT * FROM abc_table LIMIT WHAT TO ENTER HERE ";
As far as I know, what follows LIMIT has to be an integer literal. LIMIT won't even take something like 6/2, which would evaluate to an integer literal. I recommend just reading in the entire table, and then only processing how many rows you need in PHP.
$row_cnt = $result->num_rows;
$rs_size = $row_cnt - ($row_cnt % 20);
while ($rs_size > 0 && $row = mysqli_fetch_assoc($result)) {
// process a row
--$rs_size;
}
The above while loop should exit after reading the greatest number of multiples of 20 available. This approach is not too wasteful, since at most you would be reading in 19 extra rows from MySQL which you would end up not using.
You can use variables for this:
select t.*
from (select t.*, (#rn := #rn + 1) as rn
from t cross join
(select #rn := 0) params
order by ?
) t
where rn <= floor(rn / 20) * 20;
The ? is for the column used to specify the ordering, presumably something like id asc.
In MySQL 8+, you would use window functions:
select t.*
from (select t.*,
row_number() over (order by ?) as seqnum,
count(*) over () as cnt
from t
) t
where seqnum <= floor(cnt / 20) * 20;

Get the row number for a specific entry

I have a table called Leaderboard with 2 columns, PlayerName and PlayerRating. I want to be able to access a specific PlayerName, get their PlayerRating but ALSO their rank (ex: are they first, 2nd, 10th?). I did some reading around and I did find a way, but I can't seem to translate it into PHP and actually print the value. Please keep in mind that I'm not very experienced with MySQLi or PHP.
I'm currently getting my PlayerRating the following way:
$CurrentRating = $Connection->query("SELECT CurrentRating FROM Leaderboard WHERE PlayerName='$PlayerName'")->fetch_assoc()["CurrentRating"];
I can just echo $CurrentRating; and job's done. Now to find the PlayerRank I found several posts explaining how my query should also have
SET #rank=0; SELECT #rank:=#rank+1 As rank
So the beginning I suppose would be:
query("SET #rank=0; SELECT #rank:=#rank+1 As rank, CurrentRating FROM Leaderboard [...]
But I can't seem to get it working and get a rank to print properly. I tried echoing $CurrentRating["rank"] and a few variations of the following:
$CurrentRank = $Connection->query("SET #rank=0; SELECT #rank:=#rank+1 As rank, SELECT CurrentRating FROM Leaderboard WHERE PlayerName='$PlayerName'")->fetch_assoc()["rank"];
Can someone please help me write this correctly?
Thanks in advance!
Your queries should be something like these when you want ranking based on CurrentRating.
Query
SELECT
Leaderboard.PlayerName
, Leaderboard.PlayerRating
, (#rank := #rank + 1) AS rank
FROM
Leaderboard
CROSS JOIN(
SELECT
#rank := 0
)
AS
init_user_variable
ORDER BY
Leaderboard.CurrentRating DESC
When you want a ranking with where filter you should use this query inside a delivered table like this.
Query
SELECT
Leaderboard_ranked.PlayerName
, Leaderboard_ranked.PlayerRating
, Leaderboard_ranked.rank
FROM (
SELECT
Leaderboard.PlayerName
, Leaderboard.PlayerRating
, (#rank := #rank + 1) AS rank
FROM
Leaderboard
CROSS JOIN(
SELECT
#rank := 0
)
AS
init_user_variable
ORDER BY
Leaderboard.CurrentRating DESC
)
AS
Leaderboard_ranked
WHERE
Leaderboard_ranked.PlayerName = 'playername'
PHP MySQLi Syntax
$connection = mysqli_connect("host/ip", "user", "password", "database");
$result = mysqli_query($connection, [query]);
while($row = mysqli_fetch_array($result)) {
echo $row['PlayerName'] . " " . $row['PlayerRating'] . " " . $row['rank'] "<br />"
}
SELECT COUNT(*) FROM Leaderboard WHERE CurrentRating > ?
With users Current rating as a parameter should be more or less enough

Changing foreach statement to display current user's table

I have a leaderboard that i am making but am wanting to display the current user rank on the top of my page, i have been trying to make the following code to work
<?
if(! defined('BASEPATH') ){ exit('Unable to view file.'); }
$sql = $db->Query("SELECT uid, SUM(`total_clicks`) AS `clicks` FROM `user_clicks` GROUP BY uid ORDER BY `clicks` DESC");
$tops = $db->FetchArrayAll($sql);
$j = 0;
foreach($tops as $top){
$j++;
$user = $db->QueryFetchArray("SELECT id,login,email,country,coins FROM `users` WHERE `id`='".$top['uid']."'");
?>
<?=$user['login']?>
<?}?>
this returns all the usernames in a nice row i have tried the following
<?=$user['top']?>
<?=$user['j']?>
i understand its something to do with the foreach statement that needs to be changed but i also know i am missing something.
What i am trying to achieve is Rank : 31
You can generate the rank directly in your query. Then you don't need a loop to calculate the rank
SELECT uid,
SUM(`total_clicks`) AS `clicks`,
#rank := #rank + 1 as ranking
FROM `user_clicks`, (select #rank := 0) r
GROUP BY uid
ORDER BY `clicks` DESC

SQL query construction

I recently created a scoring system where the users are ordered by their points on descending basis. First I used to store ranks in a column of its own. I used to run this loop to update the rank:
$i = 1;
$numberOfRows = mysql_query('SELECT COUNT(`id`) FROM sector0_players');
$scoreboardquery = mysql_query("SELECT * FROM sector0_players ORDER BY points DESC");
while(($row = mysql_fetch_assoc($scoreboardquery)) || $i<=$numberOfRows){
$scoreid = $row['id'];
$mysql_qeury = mysql_query("UPDATE sector0_players SET scoreboard_rank = '$i' WHERE id = '$scoreid'");
$i++;
}
And it was really hard, not to mention slow to actually run this on a huge amount of users.
Instead, I tried to construct a query and ended up with this.
SET #rownum := 0;
SELECT scoreboard_rank, id, points
FROM (
SELECT #rownum := #rownum + 1 AS scoreboard_rank, id, points FROM sector0_players ORDER BY points DESC
)
as result WHERE id = '1';
But, this is just a select statement. Is there anyway I could get around it and change it so that it updates the table just as the loop does?
Please try using the following query :
set #rownum:=0;
update sector0_players set scoreboard_rank=#rownum:=#rownum+1 ORDER BY points DESC;
PHP code can be ,
mysql_query("set #rownum:=0;");
mysql_query("update sector0_players set scoreboard_rank=#rownum:=#rownum+1 ORDER BY points DESC;");
You can try using the RANK function .. I haven't actually executed the SQL, but it should work
UPDATE sector0_players
SET scoreboard_rank =
(
SELECT srank
FROM
(
SELECT id,points, RANK() OVER (ORDER BY points) AS srank
FROM sector0_players T
) D
WHERE D.id = sector0_players.id
AND D.points = sector0_players.points
)

Categories