2016-03-23 2 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 будет выполнен, поэтому я написал код ниже. Это оптимально?

То, что меня больше всего волнует, я делаю MySql UPDATE около 1000 раз. Это здорово? Должен ли я делать это по-другому? (Также вы можете оптимизировать любой другой код в функции. Как я уже говорил, я из 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

Не заглядывал в детали вашей проблемы, так как, как вы делаете, это кажется прекрасным, но, просто для «цели обсуждения», если вы хотите избежать тысячи обновлений в своем db, вам стоит подумать о другом «рейтинге» ", как столбец в вашей таблице, который ссылается на« предыдущий »или« следующий элемент ». Таким образом, ваше ранжирующее обновление повлияет только на «реклассифицированные» предметы и соседей ... – Julo0sS

ответ

4

Как вам нужно использовать ранжирование ? Может быть, вы не используете Ranks? Они могут быть легко вычислен:

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

Чтобы показать TOP 10:

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

т.д.

EDIT:

Показать полный рейтинг с номером позиции вы можете сделать это в PHP (у вас уже есть - внутри цикла, где вы выбираете строки, просто отображаете переменную $i++). Или вы можете попробовать с чистым SQL (лично мне это больше нравится):

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

Студенты смогут войти в систему и посмотреть их ранжирование, скопировав страницы (чтобы посмотреть, как они сравниваются со всеми) , Они также могут войти на свою личную страницу, которая будет иметь общий рейтинг и недавний ранг.Я думал, что подсчет ранжирования один раз после каждого теста будет лучшим способом сделать это, а не вычислять его каждый раз для тысяч просмотров каждым учеником каждый день. (Есть только 2 теста в день только для 5 учеников каждый тест.) – Rewind

+0

Я отредактировал свой ответ. – Mark

+0

Будет ли ваш метод ранжирования равным средним? Например, у 2-го и 3-го учеников в среднем 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 поля, если вы хотите заказать по недавнему среднему вместо этого.

Смежные вопросы