2016-02-08 16 views
7

IntroduzioneCome combinare queste due query per calcolare il cambio di classificazione?

Ho una tabella dei punteggi per il mio gioco che usa ranghi. La tabella dei punteggi rappresenta i punteggi più alti correnti e le informazioni sui giocatori e la tabella recente rappresenta tutti i punteggi pubblicati di recente da un utente che potrebbe avere o meno un nuovo miglior punteggio.

Il calo del punteggio viene calcolato calcolando il rango attuale del giocatore meno il rango che avevano al momento del raggiungimento dell'ultimo punteggio più alto.

L'aumento di rango viene calcolato calcolando il rango del giocatore che avevano al momento del raggiungimento del loro ultimo punteggio più alto meno il rango che avevano al momento del raggiungimento del punteggio superiore precedente.

Infine, come scritto in codice: $change = ($drop > 0 ? -$drop : $increase);


Domanda

Sto usando i seguenti due query combinate con un po 'di codice PHP per calcolare il cambiamento rango. Funziona perfettamente bene, ma a volte è un po 'lento.

Ci sarebbe un modo per ottimizzare o combinare le due query + codice PHP?

ho creato un violino SQL della prima query: http://sqlfiddle.com/#!9/30848/1

I tavoli sono pieni di contenuti già, quindi le loro strutture non devono essere modificate.

Questo è il codice di lavoro corrente:

$q = " 
      select 
      (
      select 
       coalesce(
        (
         select count(distinct b.username) 
         from recent b 
         where 
          b.istopscore = 1 AND 
          (
           (
            b.score > a.score AND 
            b.time <= a.time 
           ) OR 
           (
            b.score = a.score AND 
            b.username != a.username AND 
            b.time < a.time 
           ) 
          ) 
         ), 0) + 1 Rank 
      from scores a 
      where a.nickname = ?) as Rank, 
      t.time, 
      t.username, 
      t.score 
      from 
      scores t 
      WHERE t.nickname = ? 
      "; 

      $r_time = 0; 

      if($stmt = $mysqli->prepare($q)) 
      { 
       $stmt->bind_param('ss', $nick, $nick); 
       $stmt->execute(); 
       $stmt->store_result(); 
       $stmt->bind_result($r_rank, $r_time, $r_username, $r_score); 

       $stmt->fetch(); 

       if(intval($r_rank) > 99999) 
        $r_rank = 99999; 

       $stmt->close(); 
      } 

      // Previous Rank 
      $r_prevrank = -1; 

      if($r_rank > -1) 
      { 
       $q = " 
       select 
        coalesce(
         (
          select count(distinct b.username) 
          from recent b 
          where 
           b.istopscore = 1 AND 
           (
            (
             b.score > a.score AND 
             b.time <= a.time 
            ) OR 
            (
             b.score = a.score AND 
             b.username != a.username AND 
             b.time < a.time 
            ) 
           ) 
          ), 0) + 1 Rank 
       from recent a 
       where a.username = ? and a.time < ? and a.score < ? 
       order by score desc limit 1"; 

       if($stmt = $mysqli->prepare($q)) 
       { 
        $time_minus_one = ($r_time - 1); 

        $stmt->bind_param('sii', $r_username, $time_minus_one, $r_score); 
        $stmt->execute(); 
        $stmt->store_result(); 
        $stmt->bind_result($r_prevrank); 

        $stmt->fetch(); 

        if(intval($r_prevrank) > 99999) 
         $r_prevrank = 99999; 

        $stmt->close(); 
       } 
       $drop = ($current_rank - $r_rank); 
       $drop = ($drop > 0 ? $drop : 0); 


       $increase = $r_prevrank - $r_rank; 
       $increase = ($increase > 0 ? $increase : 0); 

       //$change = $increase - $drop; 
       $change = ($drop > 0 ? -$drop : $increase); 
      } 

      return $change; 
+0

Forse passare a PDO lo rende un po 'più veloce, ma probabilmente non farà nulla. – Tom

+0

Potresti spiegare: – gfunk

+1

Potresti definire 1. quali sono le due tabelle e 2. qual è il tuo algoritmo/matematica per il calcolo del rank? Da quello che ho capito il mio grado è il numero di voci recenti di altre persone che hanno ottenuto un punteggio più alto * e * avvenuto prima (in tempo) del mio record nella tabella "punteggi". più ignorando! istopscoes – gfunk

risposta

3

Se si separa il punteggio superiore corrente in una nuova tabella, mentre tutti i dati grezzi sono disponibili nei punteggi recenti .. si è effettivamente prodotta una tabella di riepilogo.

Perché non continuare a riepilogare e riepilogare tutti i dati necessari?

E 'quindi solo un caso di quello che si fa a sapere quando si può conoscere:

  • Classifica attuale - dipende da altri file
  • Classifica sul nuovo punteggio più alto - può essere calcolato come Rank corrente e memorizzato al momento dell'inserimento/aggiornamento
  • Graduatoria precedente del punteggio più alto - Può essere trasferito dal vecchio 'posizionamento sul nuovo miglior punteggio' quando viene registrato un nuovo punteggio più alto.

che vorrei cambiare la vostra tabella punteggi per includere due nuove colonne:

  • punteggi - id, punteggio, nome utente, nickname, il tempo, rank_on_update, old_rank_on_update

e regolare queste colonne man mano che aggiorni/inserisci ogni riga. Sembra che tu abbia già delle query che possono essere utilizzate per eseguire il backfit di questi dati per la tua prima iterazione.

Ora le vostre domande diventano molto più semplice

per Posizione dal punteggio:

SELECT COUNT(*) + 1 rank 
    FROM scores 
WHERE score > :score 

Da username:

SELECT COUNT(*) + 1 rank 
    FROM scores s1 
    JOIN scores s2 
    ON s2.score > s1.score 
WHERE s1.username = :username 

E Cambio Posizione diventa:

$drop = max($current_rank - $rank_on_update, 0); 
    $increase = max($old_rank_on_update - $rank_on_update, 0); 
    $change = $drop ? -$drop : $increase; 
012.

UPDATE

  • Commento 1 + 3 - Ops, potrebbe aver incasinato che fino .. hanno cambiato sopra.
  • Commento 2 - Non corretto, se si tiene aggiornati i punteggi (tutti gli ultimi punteggi più alti) al volo (ogni volta che viene registrato un nuovo punteggio elevato) e presupponendo che vi sia una riga per utente, al momento del calcolo del rango attuale dovrebbe essere semplicemente un conteggio di punteggi più alti rispetto al punteggio dell'utente (+1). Dovrebbe essere sperabilmente in grado di evitare quella pazza query una volta che i dati sono aggiornati!

Se si insiste sulla separazione dal tempo, questo lavoro per una nuova riga se non avete ancora aggiornato la riga:

SELECT COUNT(*) + 1 rank 
    FROM scores 
WHERE score >= :score 

L'altra interrogazione sarebbe diventato:

SELECT COUNT(*) + 1 rank 
    FROM scores s1 
    JOIN scores s2 
    ON s2.score > s1.score 
    OR (s2.score = s1.score AND s2.time < s1.time) 
WHERE s1.username = :username 

Ma almeno proverei unione per le prestazioni:

SELECT SUM(count) + 1 rank 
    FROM ( 
    SELECT COUNT(*) count 
     FROM scores s1 
     JOIN scores s2 
     ON s2.score > s1.score 
    WHERE s1.username = :username 
    UNION ALL 
    SELECT COUNT(*) count 
     FROM scores s1 
     JOIN scores s2 
     ON s2.score = s1.score 
     AND s2.time < s1.time 
    WHERE s1.username = :username 
     ) counts 

Un indice su (score, time) sarebbe di aiuto qui.

Personalmente mi piacerebbe risparmiare un mal di testa e tenere stessi punteggi allo stesso valore (abbastanza standard credo) .. Se vuoi che la gente di essere in grado di rivendicare primi diritti di vanteria solo assicurarsi di ordine di data ASC su qualsiasi segnare i grafici e includere l'ora sul display.

+0

Sembra un'ottima risposta. La prima parte ha perfettamente senso per me, tuttavia, la seconda parte sotto "Ora le tue domande diventano molto più semplici", non comprendo appieno. Potresti per favore approfondire su questo? Perché suggeriresti di cambiare il mio ultimo calcolo del cambio di rango da '$ change = $ increase - $ drop;' a '$ change = $ old_rank_on_update - $ rank'? E come funziona e ha senso per i giocatori? – Z0q

+0

Per calcolare il rank corrente di un giocatore, credo di aver bisogno di usare la query corrente che utilizzo, perché contiene tutti i controlli, incluso il tempo, ecc. Corretto? – Z0q

+0

Siamo spiacenti, il calcolo finale attualmente è in realtà '$ change = ($ drop> 0? - $ drop: $ increase);'. – Z0q

0

ho speso un sacco di tempo a cercare di capire cosa la logica rango è e mettere in un commento su di esso. Nel frattempo, ecco una query join che è possibile eseguire sul proprio dati - credo che la soluzione sarà qualcosa di qualcosa di questo tenore:

SELECT s.username, count(*) rank 
FROM scores s LEFT JOIN recent r ON s.username != r.username 
WHERE r.istopscore 
AND r.score >= s.score 
AND r.time <= s.time 
AND (r.score-s.score + s.time-r.time) 
GROUP BY s.username 
ORDER BY rank ASC; 

+----------+------+ 
| username | rank | 
+----------+------+ 
| Beta  | 1 | 
| Alpha | 2 | 
| Echo  | 3 | 
+----------+------+ 

(notare che la scorsa ed è solo per essere sicuri di non tengono conto di r.score == s.score & & r.time == s.time - che credo sarebbe un gioco di "cravatta")

+0

Grazie per aver dedicato del tempo. Lo scopo di questa query è calcolare il rango? Non funziona. E inoltre, ho bisogno di calcolare la differenza di rango. – Z0q

0

io non sono un ragazzo di MySQL, ma credo che l'utilizzo di self-join per la classifica è una cattiva pratica in qualsiasi RDBMS. Dovresti considerare l'utilizzo delle funzioni di classifica. Ma non ci sono funzionalità di classificazione in MySQL. Ma ci sono workarounds.

0

Ci sono alcune ipotesi che devono essere fatte qui per andare avanti con questo. Presumo che la tabella dei punteggi abbia una sola voce per "username" che è in qualche modo equivalente a un nickname.

Prova questo,

Se avessi un DB di lavoro, questa sarebbe stata veloce per capire e testare, ma fondamentalmente si sta prendendo la 'domanda secondaria' si esegue nel campo selezionato e si sta costruendo un tabella temporanea con TUTTI i record e filtrandoli.

 select a.nickname 
      , count(distinct b.username) as rank 
      , t.time 
      , t.username 
      , t.score 
     from 
     ( 
       select 
        a.nickname 
        , b.username 
       from (select * from scores where nickname=?) a 
        left join (select * from recent where istopscore = 1) as b 
       on (
         b.score > a.score and b.time <= a.time -- include the b record if the b score is higher 
         or 
         b.score = a.score and b.time < a.time and a.username != b.username -- include b if the score is the same, b got the score before a got the score 
       ) 
     ) tmp 
     join scores t on (t.nickname = tmp.nickname) 
     where t.nickname = ? 

Non ho tentato di affrontare la logica in seguito, è possibile utilizzare la stessa teoria, ma non vale la pena provare a meno che non si può confermare che questo metodo restituisce le righe corrette.

Se si desidera approfondire, è necessario creare alcuni set di dati e impostare completamente SQL Fiddle.

+0

Grazie. Ci proverò. Il 'username' è usato per gli indirizzi e-mail che identificano gli utenti e anche il' nickname' è univoco, ma può essere modificato. Sì, la tabella dei punteggi contiene una voce per utente. C'è una cosa di cui sono preoccupato: la tabella 'recent' è enorme (200K + righe). Preferirei controllare se i nomi utente siano identici invece dei nickname ('t.nickname = a.nickname'). Ma va bene. – Z0q

+0

Sulla riga prima dell'ultima riga, si ferma: '# 1054 - Colonna sconosciuta 'a.nickname' in 'on clausola''. Non capisco come stai cercando di combinare e unire queste due query, quindi non so come risolverlo. Sembra che MySQL non permetta l'uso dei campi della query SELECT interna al di fuori di esso. – Z0q

+0

Sull'ultima riga, invece di a.nickname avrebbe dovuto essere tmp.nickname Ho aggiornato l'SQL. Buona fortuna con esso –

Problemi correlati