2016-03-23 63 views
5

我不是一个伟大的PHP编码器(我来自C++)。我只使用PHP进行数据库输入。PhP,MySql - 优化代码

我有以下数据库:

UserId (an unique int) 
AsyncPointsAverage (float) 
AsyncPointsAverageRank (a position based on the value immediately above) 
AsyncPointsRecentAverage (float an average for the last 5 tests only) 
AsyncPointsRecentAverageRank (a position based on the value immediately above) 

有在该表中大约1000-1500项。每天早上和下午5人进行测试,影响他们的整体平均水平和最近的平均水平。 (这是其他地方更新的内容,但这里没有显示。)之后,对这5个人进行计算,然后所有1000-1500的排名都会生效,所以我写下了下面的代码。它是最佳的吗?

我最关心的是我正在做1000次的MySql UPDATE。这很棒吗?我应该以另一种方式去做吗? (也觉得免费优化功能的任何其他代码。正如我说的,我是从C++背景,所以真的不知道PHP的细微差别。)

// Sorts by array entry 1 
function ReRankCompareAverage($a, $b) 
{ 
    if($a[1] == $b[1]) return 0; 
    else return ($a[1] > $b[1] ? 1 : -1); 
} 
// Sorts by array entry 2 
function ReRankCompareAverageRecent($a, $b) 
{ 
    if($a[2] == $b[2]) return 0; 
    else return ($a[2] > $b[2] ? 1 : -1); 
} 

function ReRank($db) 
{ 
    $i = 0, $j = 0; 
    $usersARR = null; 

    $stmt = $db->prepare("SELECT UserId, AsyncPointsAverage, AsyncPointsRecentAverage FROM studenttable"); 
    $stmt->execute(); 
    if($stmt && isset($stmt) && $stmt->rowCount() > 0) 
    { 
     $i = 0; 
     while(($row = $stmt->fetch(PDO::FETCH_ASSOC))) 
     { 
      $usersARR[$i][0] = intval($row['UserId']); 
      $usersARR[$i][1] = floatval($row['AsyncPointsAverage']); 
      $usersARR[$i][2] = floatval($row['AsyncPointsRecentAverage']); 
      $i++; 
     } 
    } 
    $stmt->closeCursor(); // mysql_free_result equivalent 

    // The first pass of $j == 3 does the ranking by Average, filling position $usersARR[][3] with that rank 
    // The second pass of $j == 4 does the ranking by AverageRecent, filling position $usersARR[][4] with that rank 
    for($j = 3, $j <= 4; $j++) 
    { 
     $iCompare = $j == 3 ? 1 : 2; 

     usort($usersARR, $j == 3 ? "ReRankCompareAverage" : "ReRankCompareAverageLast"); 
     $count = count($usersARR); 
     if($count > 0) 
     { 
      // Start it off, with the person with the highest average is rank 1 
      $usersARR[$count - 1][$j] = 1; // Position $j is filled with the rank 
      // Now loop starting from the second one down 
      for($i = $count - 2, $rank = 1; $i >= 0; $i--) 
      { 
       // Only change the rank if the next one down is strictly lower than the one above, otherwise will share the same rank 
       if($usersARR[$i][$iCompare] < $usersARR[$i+1][$iCompare]) $rank = $count - $i; // Otherwise keep the same rank, because they are equal 
       $usersARR[$count - 1][$j] = $rank; 
      } 
     } 
    } 

    // Now $usersARR is filled with the correct rankings, and they are asscoiated with $UserId 
    // Now we must put all of these rankings into the database 
    $count = count($usersARR); 
    for($i = 0; $i < $count; $i++) 
    { 
     $stmt = $db->prepare("UPDATE studenttable SET AsyncPointsAverageRank=:AsyncPointsAverageRank, AsyncPointsRecentAverageRank=:AsyncPointsRecentAverageRank " 
         . "WHERE UserId=:UserId"); 
     $stmt->execute(array(':AsyncPointsAverageRank' => $usersARR[$i][3], 
         ':AsyncPointsRecentAverageRank' => $usersARR[$i][4], 
         ':UserId' => $usersARR[$i][0])); 
    } 
} 
+0

您可以使用事务并执行所有更新。我不确定MyISAM是否支持交易,但InnoDb是否支持交易。 – frz3993

+0

您的代码是注入安全的,并且对于任何现代数据库服务器运行一千个小型更新查询都不成问题。我会说你很好。如果你想进一步优化,你在错误的StackExchange站点。 :) –

+0

没有看到你的问题的细节,因为你做的方式似乎很好,但只是为了“讨论的目的”,如果你想在你的数据库中避免千次更新,你应该考虑另一个“排名“系统,就像表格中引用”上一个“或”下一个元素“的列。通过这种方式,您的排名更新只会影响“reclassed”项目和邻居... – Julo0sS

回答

4

需要如何使用排名?也许你存储Ranks是不必要的?他们可能很容易计算:

SELECT COUNT(*) 
FROM studenttable 
WHERE AsyncPointsAverage > $currentUserVariableAsyncPoints 

要显示TOP 10:

SELECT * FROM studenttable ORDER BY AsyncPointsAverage DESC LIMIT 0,10 

编辑:

,以显示与位置编号完整的排名,你可以做到这一点在PHP(你已经有了 - 在循环内部,你可以获取行,只显示$i++变量)。或者你可以用纯SQL尝试(我个人比较喜欢):

SET @rank=0; SELECT @rank := @rank +1 AS rank, UserId, AsyncPointsAverage 
FROM studenttable 
ORDER BY AsyncPointsAverage DESC 
+0

学生将能够登录到系统并查看他们的排名,通过翻页浏览(以了解他们与其他人的比较) 。他们还可以登录到自己的个人页面,该页面的总体排名和最近的排名。我认为在每次测试后计算一次排名是最好的方法,而不是每次为每个学生每天观看数千次视图时计算排名。 (每次只有5名学生每天只进行2次考试。) – Rewind

+0

我编辑了我的答案。 – Mark

+0

您的排名方法是否将相同的排名等于平均值​​?比如说第二和第三名学生平均有88%。他们都将排名第二。然后下一个人将排在第4位(即完全排除第3位,因为2人有第二位)。 – Rewind

1

只是对马克的回答展开,你不需要每次添加一个测试结果的时间重新计算排名。这当然是功能性的,但它不是最佳的。最好的方法是在显示它时计算排名。如果您希望允许学生获得相同的结果和相同的排名,则可以始终使用PHP计算排名。

SQL:

SELECT 
    UserId, 
    AsyncPointsAverage, 
    AsyncPointsAverageRank 
FROM 
    studenttable 
ORDER BY 
    AsyncPointsAverage DESC 

PHP:

$stmt = $db->prepare("SEE ABOVE..."); 
$stmt->execute(); 

if($stmt && isset($stmt) && $stmt->rowCount()) { 
    $rank = 1; 
    $last_grade = -1; 

    while(($row = $stmt->fetch(PDO::FETCH_ASSOC))) { 
     $usersARR[$i][0] = intval($row['UserId']); 
     $usersARR[$i][1] = floatval($row['AsyncPointsAverage']); 
     $usersARR[$i][2] = floatval($row['AsyncPointsRecentAverage']); 

     if($usersARR[$i][1] < $last_grade) { 
      $rank++; 
     } 

     $usersARR[$i][3] = $rank; 

     $last_grade = $usersARR[$i][1]; 
    } 
} 

你只需要改变字段读取和ORDER BY领域,如果你想通过最近的平均订购吧。