-1

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

  1. me 1111
  2. me 1111
  3. 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;
 }
 }
 ?>  
Community
  • 1
  • 1
icehotter
  • 1
  • 3
  • 3
    Please, [stop using `mysql_*` functions](http://stackoverflow.com/questions/12859942/why-shouldnt-i-use-mysql-functions-in-php). They are no longer maintained and are [officially deprecated](https://wiki.php.net/rfc/mysql_deprecation). Learn about [prepared statements](http://en.wikipedia.org/wiki/Prepared_statement) instead, and consider using PDO, [it's not as hard as you think](http://jayblanchard.net/demystifying_php_pdo.html). – Jay Blanchard May 18 '15 at 18:11
  • There's nothing in the script that guarantees that the "oldest" of two scores will be ranked before a newer one. To get that kind of guarantee, you would need to add to the `ORDER BY` clause of your query, we're just taking a guess that there's a DATETIME or TIMESTAMP column, and taking a guess at it's name here: **`ORDER BY score DESC, datetime_score_achieved ASC`**. Without that, there is *no* guarantee of the order of rows with "tied" scores. – spencer7593 May 18 '15 at 21:27
  • Not a problem, I will test it to death see what happens, its been working that way for some time so hopefully it remains so, if not I will ORDER by date time achieved. – icehotter May 18 '15 at 21:46
  • Oh and thanks again! Spencer your a legend! – icehotter May 18 '15 at 21:50

1 Answers1

0

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
spencer7593
  • 106,611
  • 15
  • 112
  • 140
  • Thanks Spencer7593 I appreciate the help, have updated the Original Post and tried your suggestions but I'm still not getting very far. – icehotter May 18 '15 at 20:37
  • The **`$result->fetchAssoc`** is an example using mysqli object oriented style; that's not going to work in your code. You'd need to use an equivalent mysql interface function to perform the fetch. You want a pattern like that shown in Example #3 in the documentation. [**http://php.net/manual/en/function.mysql-fetch-array.php**](http://php.net/manual/en/function.mysql-fetch-array.php). There's *no need* to invoke the `mysql_num_results` function. – spencer7593 May 18 '15 at 20:44
  • Working well so far with this at least I can get the results for the first entry, just trying to figure out how to move on if(!$row['name'] == "you") you said skip in the comment, I have tried "continue;" and "$row[rank]++;" in the else statement... – icehotter May 18 '15 at 21:09
  • scratch that last one its working fine it was the $limit i was specifying from the application that was set to 1 meaning there were no other rows to move on to in this case continue; works perfectly – icehotter May 18 '15 at 21:14
  • Maybe it would be clearer if you left off the `else` block entirely. I added an empty `else` block just to have someplace to put a comment line, in a feeble attempt to make the intent clearer, to give the reader an idea of what was happening. The else block isn't really required. – spencer7593 May 18 '15 at 21:17
  • to be honest without it I wouldn't have known to move on if the "if" statement wasn't satisfied, it made the intent very clear I now have a working model and I am extremely grateful! – icehotter May 18 '15 at 21:35