2016-03-23 12 views
5

Nie jestem świetnym koderem php (pochodzę z C++). Używam php tylko do wpisania bazy danych.PhP, MySql - Optymalizowanie kodu

Mam bazę danych z następujących czynności:

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) 

Istnieje około 1000/00 zapisy w tej tabeli. Każdego ranka i popołudnia 5 osób podejmuje test, który wpływa na ich średnią średnią i ostatnią średnią. (Zostało to zaktualizowane w innym miejscu, ale nie zostało to tutaj pokazane.) Po tym zostanie obliczona dla tych 5 osób, wtedy zostaną przeprowadzone rankingi wszystkich 1000-1500, więc napisałem poniższy kod. Czy jest optymalny?

Najbardziej niepokoi mnie to, że robię aktualizację MySql około 1000 razy. Czy to jest świetne? Czy powinienem to robić w inny sposób? (Również zachęcamy do optymalizacji inny kod w funkcji. Jak mówię, jestem z C++ tła, tak naprawdę nie wiem niuanse 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

Możesz użyć transakcji i wykonać wszystkie aktualizacje. Nie jestem pewien, czy MyISAM obsługuje transakcje, ale robi to InnoDb. – frz3993

+0

Twój kod jest bezpieczny w użyciu, a wykonanie tysiąca małych zapytań o aktualizację nie stanowi problemu dla żadnego nowoczesnego serwera bazy danych. Powiedziałbym, że nic ci nie jest. Jeśli chcesz dalszej optymalizacji, znajdujesz się na niewłaściwej stronie StackExchange. :) –

+0

Nie sprawdzałem szczegółów twojego problemu, ponieważ sposób, w jaki to robisz, wydaje się w porządku, ale, tylko dla "celu dyskusji", jeśli chcesz uniknąć tysiąca aktualizacji w twojej db, powinieneś rozważyć inny "ranking" "system, podobnie jak kolumna w tabeli, która odnosi się do" poprzedniego "lub" następnego elementu ". W ten sposób twoja aktualizacja rankingu wpłynie tylko na "reclassed" przedmioty i sąsiadów ... – Julo0sS

Odpowiedz

4

Jak trzeba użyć ranking ? Może przechowujesz rankingi niepotrzebne? Mogą być łatwo obliczyć:

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

Aby pokazać TOP 10:

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

itp

EDIT:

Aby wyświetlić pełną rankingu z pozycji numer można też zrobić to w PHP (już go masz - wewnątrz pętli, do której pobierane są wiersze, wyświetlana jest zmienna $i++). Można też spróbować z czystym SQL (osobiście lubię je lepiej):

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

Studenci będą mogli zalogować się do systemu i zobaczyć ich rankingi, poprzez przeglądanie stron (aby zobaczyć, jak porównać wszystkich innych) . Mogą również zalogować się na swoją własną stronę, która będzie miała ogólną pozycję i ostatnią rangę.Myślałem, że obliczenie rankingu raz po każdym teście byłoby najlepszym sposobem, aby to zrobić, zamiast obliczać go za każdym razem dla tysięcy wyświetleń każdego studenta każdego dnia. (Są tylko 2 testy dziennie dla zaledwie 5 uczniów w każdym teście.) – Rewind

+0

Edytowałem swoją odpowiedź. – Mark

+0

Czy twoja metoda rankingowa da taką samą rangę równym wartościom średnim? Np. Studenci 2 i 3 maja średnio 88%. Obaj zajmą 2. miejsce. Następnie następna osoba znajdzie się na czwartym miejscu (tj. Całkowicie pominie trzecią kategorię, ponieważ 2 osoby mają drugą.) – Rewind

1

Wystarczy rozwinąć na odpowiedź Marka, nie trzeba przeliczyć rangi za każdym razem dodać wynik testu. Jest z pewnością funkcjonalny, ale nie jest optymalny. Najlepszym sposobem jest obliczenie rangi podczas jej wyświetlania. Jeśli chcesz pozwolić uczniom, którzy mają taki sam wynik i taką samą rangę, zawsze możesz obliczyć pozycję w 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]; 
    } 
} 

Trzeba tylko zmienić pola odczytu i pole ORDER BY jeśli chcesz zamówić przez niedawnego średniej zamiast.