sql - How do I assign same rank to users with the same score with PHP?

Solution:

Talking points

So there are a few things with your code that aren't best practice or are a bit confusing.

  • You should really use prepared statements with you SQL queries. They offer far better protection when using user derived data (it isn't clear whether your search parameters are from user input or from other code)
  • If you're using $variables in your double quoted strings it's good practice to use {curly braces} to encompass them. It solves a variety of issues with PHP using the correct variable and also makes it easier to read and maintain
  • As others have said you haven't actually shown where $Reg has come from - so I have ignored it in the following code

Solution

Table/Data

Assuming that you have the following data in your table...

Table: results

StudentReg | school_session | Term | class | subjectID | Average

Sorted data returned by query

{-code-7}

Code

I have adapted the code to use prepared statements.

variables

Presumably these are declared somewhere further up your code...

{-code-9}

mysqli

{-code-10}

PDO

{-code-11}

Output

{-code-12}

Answer

-+

Answer

-----+------+-------+

Answer

-+

Answer

--- 1 | 1022 | 3 | 37 | 66 | 121 2 | 1022 | 3 | 37 | 66 | 115 3 | 1022 | 3 | 37 | 66 | 129 4 | 1022 | 3 | 37 | 66 | 121 5 | 1022 | 3 | 37 | 66 | 121 6 | 1022 | 3 | 37 | 66 | 117 7 | 1022 | 3 | 37 | 66 | 129 8 | 1022 | 3 | 37 | 66 | 125 9 | 1022 | 3 | 37 | 66 | 120 10 | 1022 | 3 | 37 | 66 | 118 11 | 1022 | 3 | 37 | 66 | 125 12 | 1022 | 3 | 37 | 66 | 124 13 | 1022 | 3 | 37 | 66 | 125 14 | 1022 | 3 | 37 | 66 | 125|||StudentReg | school_session | Term | class | subjectID | Average

Answer

-+

Answer

-----+------+-------+

Answer

-+

Answer

--- 2 | 1022 | 3 | 37 | 66 | 115 6 | 1022 | 3 | 37 | 66 | 117 10 | 1022 | 3 | 37 | 66 | 118 9 | 1022 | 3 | 37 | 66 | 120 1 | 1022 | 3 | 37 | 66 | 121 5 | 1022 | 3 | 37 | 66 | 121 4 | 1022 | 3 | 37 | 66 | 121 8 | 1022 | 3 | 37 | 66 | 125 12 | 1022 | 3 | 37 | 66 | 124 11 | 1022 | 3 | 37 | 66 | 125 13 | 1022 | 3 | 37 | 66 | 125 14 | 1022 | 3 | 37 | 66 | 125 3 | 1022 | 3 | 37 | 66 | 129 7 | 1022 | 3 | 37 | 66 | 129|||prepared|||$session = 1022; $term = 3; $class = 37; $subjectID = 66;|||// SQL query to SELECT "Average" and "StudentReg" from "results" // ?s are a place holder for values we'll bind later $sql = " SELECT StudentReg, Average FROM `results` WHERE school_session = ? AND Term = ? AND class = ? AND subjectID = ? ORDER BY Average DESC "; $query = $mysqli->prepare(); // Prepare the query $query->bind_param("iiii", $session, $term, $class, $subjectID); // Bind search values to parameters ("iiii" : one "i" for each variable, set's data type to "int") $query->execute(); // Run the query $query->Store_result(); // Store the result set $query->bind_result($studentreg, $average); // Bind returned rows to variables $score_ends = array(1 => "st", 2 => "nd", 3 => "rd", 4 => "th"); $last_value = false; // Initialise last value of result set ### $counter = 0; // Initialise the counter $rank = 0; while($query->fetch()){ // Loop through the result set ### $counter++; // Increment the counter ### // "if" statement using ternary logic ### // Sets the rank as either the counter OR the previous rank if it's the same ### $rank = ($last_value == false || $last_value <> $average) ? $counter : $rank; // "if" statement using ternary logic // Pre-increments (++ before variable) rank if the Average is not the same as previous // ...or leaves as is if it is the same $rank = ($last_value == false || $last_value <> $average) ? ++$rank: $rank; $end = ($rank > 3) ? $score_ends[4] : $score_ends[$rank]; // Sets the suffix to the rank using ternary logic echo "{$rank}{$end} {$studentreg}<br>"; // Prints the result; e.g. 1st 2 {2 == the StudentReg, presumably you'll update that to be a name or something} $last_value = $average; // Sets current average as last value ready for next loop }|||// SQL query to SELECT "Average" and "StudentReg" from "results" // ?s are a place holder for values we'll bind later $sql = " SELECT StudentReg, Average FROM `results` WHERE school_session = ? AND Term = ? AND class = ? AND subjectID = ? ORDER BY Average DESC "; $query = $PDO->prepare(); // Prepare the query $query->execute([$session, $term, $class, $subjectID]); // Execute the query and bind variables to place holders $score_ends = array(1 => "st", 2 => "nd", 3 => "rd", 4 => "th"); $last_value = false; // Initialise last value of result set ### $counter = 0; // Initialise the counter $rank = 0; while($query->fetch()){ // Loop through the result set ### $counter++; // Increment the counter ### // "if" statement using ternary logic ### // Sets the rank as either the counter OR the previous rank if it's the same ### $rank = ($last_value == false || $last_value <> $row["Average"]) ? $counter : $rank; // "if" statement using ternary logic // Pre-increments (++ before variable) rank if the Average is not the same as previous // ...or leaves as is if it is the same $rank = ($last_value == false || $last_value <> $row["Average"]) ? ++$rank: $rank; $end = ($rank > 3) ? $score_ends[4] : $score_ends[$rank] ; // Sets the suffix to the rank using ternary logic echo "{$rank}{$end} {$row["StudentReg"]}<br>"; // Prints the result; e.g. 1st 2 {2 == the StudentReg, presumably you'll update that to be a name or something} $last_value = $row["Average"]; // Sets current average as last value ready for next loop }|||1st 2 2nd 6 3rd 10 4th 9 5th 1 5th 5 5th 4 8th 8 9th 12 10th 11 10th 13 10th 14 13th 3 13th 7

Answer

Solution:

If you are running MySQL 8.0, you can do the ranking directly in the database, using window functions:

select r.*, rank() over(order by average desc) rn 
from results r
where 
    school_session = ?
    and term = ?
    and class = ?
    and  subjectid = ?
order by average desc

rank() assigns the same number to ties.

Note that I changed the query to use parameters rather than concatenating php variables in the query string: this is both safer and more efficient.

Source