2016-03-23 18 views
5

Non sono un ottimo programmatore di php (vengo da C++). Sto usando php solo per l'inserimento nel database.PhP, MySql - Codice ottimizzazione

Ho un database con il seguente:

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) 

Ci sono circa 1000-1500 voci in quella tabella. Ogni mattina e pomeriggio 5 persone effettuano un test che influisce sulla media generale e sulla media recente. (Questo è aggiornato altrove, ma non mostrato qui.) Dopo che viene calcolato per quelle 5 persone, verranno effettuate le classifiche di tutti i 1000-1500, quindi ho scritto il codice qui sotto. È ottimale?

La cosa a cui sono più interessato è che sto facendo un UPDATE MySql circa 1000 volte. È fantastico? Dovrei farlo in un altro modo? (Inoltre, sentitevi liberi di ottimizzare qualsiasi altro codice nella funzione. Come ho detto, io sono da un background C++, quindi non so davvero le sfumature del 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

È possibile utilizzare la transazione e fare tutti gli aggiornamenti. Non sono sicuro che MyISAM supporti la transazione ma InnoDb lo fa. – frz3993

+0

Il tuo codice è sicuro per l'iniezione e l'esecuzione di migliaia di piccole query di aggiornamento non è un problema per nessun server di database moderno. Direi che stai bene. Se desideri ulteriore ottimizzazione, sei sul sito StackExchange sbagliato. :) –

+0

Non ha esaminato i dettagli del tuo problema poiché il modo in cui lo stai facendo sembra soddisfacente ma, solo per "scopo di discussione", se vuoi evitare un migliaio di aggiornamenti nel tuo db, dovresti forse considerare un altro "ranking" "sistema, come una colonna nella tabella che si riferisce al" precedente "o al" prossimo elemento ". In questo modo, l'aggiornamento del tuo ranking avrà effetto solo sugli oggetti "riclassificati" e sui vicini ... – Julo0sS

risposta

4

Come è necessario utilizzare la classifica ? Forse depositi Ranks non necessari? Potrebbero essere facilmente calcolati:

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

Per mostrare TOP 10:

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

ecc

EDIT:

Per mostrare classifica completa con numero di posizione è possibile farlo in PHP (ce l'hai già - il ciclo interno in cui recuperi le righe mostra solo la variabile $i++). Oppure si può provare con puro SQL (personalmente mi piace di più):

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

Gli studenti saranno in grado di accedere al sistema e vedere le loro classifiche, scorrendo tra le pagine (per vedere come si confrontano con tutti gli altri) . Possono anche accedere alla propria pagina personale che avrà il loro rango complessivo e il rango recente.Ho pensato di calcolare le classifiche una volta dopo ogni test sarebbe il modo migliore per farlo, piuttosto che calcolarlo ogni volta per le migliaia di visualizzazioni di ogni studente ogni giorno. (Ci sono solo 2 test al giorno per soli 5 studenti per ogni test.) – Rewind

+0

Ho modificato la mia risposta. – Mark

+0

Il tuo metodo di classificazione darà lo stesso valore a parità di medie? Ad esempio, il 2 ° e il 3 ° studente hanno una media dell'88%. Saranno entrambi classificati al 2 ° posto. Quindi la prossima persona sarà classificata al 4 ° posto (cioè non ci sarà il terzo grado assoluto, perché 2 persone hanno il secondo posto.) – Rewind

1

Giusto per espandere su risposta di Marco, non occorre ricalcolare il rango ogni volta che si aggiunge un risultato del test. È certamente funzionale ma non è ottimale. Il modo migliore è calcolare il punteggio quando lo si visualizza. Se vuoi consentire agli studenti che hanno lo stesso risultato e lo stesso rango, puoi sempre calcolare il punteggio in 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]; 
    } 
} 

Hai solo bisogno di modificare i campi di lettura e il campo ORDER BY se volete ordinare da recenti media, invece.