2016-02-08 4 views
7

ВведениеКак объединить эти два запроса для вычисления изменения ранга?

У меня есть таблица рекордов для моей игры, в которой используются ранги. Таблица баллов представляет текущие рекорды и информацию о игроке, а недавняя таблица представляет все недавно опубликованные оценки пользователем, который может быть или не быть новым верхним счетом.

Снижение уровня рассчитывается путем вычисления текущего ранга игрока за вычетом того, с какого числа они были в пути до достижения последней оценки.

Увеличение ранга рассчитывается путем расчета ранга игрока, который он имел во время достижения своего последнего максимального результата, за вычетом ранга, который у них был на момент достижения своего предыдущего верхнего балла.

Наконец, как написано в коде: $change = ($drop > 0 ? -$drop : $increase);


Вопрос

Я использую следующие два запроса в сочетании с небольшим количеством PHP кода для вычисления изменения ранга. Он работает отлично, но иногда немного медленнее.

Был ли способ оптимизировать или объединить два запроса + PHP-код?

Я создал SQL скрипку первого запроса: http://sqlfiddle.com/#!9/30848/1

Таблицы заполняются содержанием уже, так что их структуры не должны быть изменены.

Это текущий рабочий код:

$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

Возможно, переход на PDO делает его немного быстрее, но, вероятно, он ничего не сделает. – Tom

+0

Не могли бы вы объяснить: – gfunk

+1

Не могли бы вы определить 1. что представляют собой две таблицы и 2. что ваш алгоритм/математика для вычисления ранга? Из того, что я понял, мой рейтинг - это число новых записей других народов, которые набрали больше * и *, произошедших раньше (по времени), чем моя запись в таблице «баллов». плюс игнорирование! istopscoes – gfunk

ответ

3

Если вы отделяете текущий верхний балл с новой таблицей, тогда как все исходные данные доступны в последних баллах. Вы фактически создали сводную таблицу.

Почему бы не продолжить обобщение и обобщение всех необходимых данных?

Это тогда только случай того, что вы знаете, и когда вы можете знать это:

  • Текущий ранг - Зависит от других строк
  • место на новый рекорд - может быть рассчитана как текущий ранг и сохраняется во время вставки/обновления
  • Предыдущий ранг на первое место - может быть перенесен из старого ранга на новый верхний балл, когда записывается новый верхний балл.

Я бы изменить таблицу рекордов, чтобы включить две новые колонки:

  • баллов - ID, оценка, имя пользователя, имя, время, rank_on_update, old_rank_on_update

и настроить эти столбцы при обновлении/вставке каждой строки. Похоже, у вас уже есть запросы, которые можно использовать для привязки этих данных для вашей первой итерации.

Теперь ваши запросы становятся намного проще

Чтобы получить звание от балла:

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

От пользователя:

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

И изменение ранга становится:

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

ОБНОВЛЕНИЕ

  • Комментарий 1 + 3 - К сожалению, возможно, испортили, что до .. изменились выше.
  • Комментарий 2 - Неправильно, если вы держите оценки (все последние высокие оценки) в актуальном состоянии на лету (каждый раз, когда записывается новый высокий балл) и при условии, что на каждый пользователь имеется одна строка, в то время для вычисления текущего ранга просто должно быть количество баллов выше, чем оценка пользователя (+1). Должен надеяться, что удастся избежать этого сумасшедшего запроса, как только данные будут обновлены!

Если вы настаиваете на разделении по времени, это будет работать на новой строке, если вы еще не обновили ряд еще:

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

Другой запрос станет:

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 

Но я бы хотя бы попытался объединиться для повышения эффективности:

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 

Индекс на (score, time) поможет здесь.

Лично я бы сэкономил себе головную боль и оставил одни и те же оценки в одном и том же ранге (довольно стандартный, я считаю). Если вы хотите, чтобы люди могли претендовать на первые права бахвальства, просто убедитесь, что вы заказываете по времени ASC на любом диаграммы баллов и включают время на дисплее.

+0

Звучит как отличный ответ. Первая часть полностью имеет смысл для меня, однако, вторая часть в разделе «Теперь ваши запросы становятся намного проще», я не совсем понимаю. Не могли бы вы рассказать об этом? Почему вы предлагаете изменить мой окончательный расчет изменения ранга с '$ change = $ увеличение - $ drop?' На '$ change = $ old_rank_on_update - $ rank'? И как это работает и имеет смысл для игроков? – Z0q

+0

Чтобы вычислить текущий ранг игрока, я считаю, что мне нужно использовать текущий запрос, который я использую, потому что он содержит все проверки, включая время и т. Д. Правильно? – Z0q

+0

Извините, окончательный расчет в настоящее время фактически '$ change = ($ drop> 0? - $ drop: $ Increment);'. – Z0q

0

Я потратил много времени, пытаясь выяснить, что ранг логика и положить в комментарии по этому поводу. В то же время, здесь присоединиться запрос, который вы можете работать на ваших данных - я думаю, что ваше решение будет что-то что-то на этот счет:

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 | 
+----------+------+ 

(обратите внимание, что в прошлом и просто убедиться, что вы не учитывают r.score == s.score & & r.time == s.time - что я думаю, было бы «галстуковой» игрой?)

+0

Спасибо, что нашли время. Является ли цель этого запроса для вычисления ранга? Это не работает. А также, мне нужно рассчитать разницу в ранге. – Z0q

0

Я не парень MySQL, но я думаю, что использование self-join для ранжирования является плохой практикой в ​​любой СУБД. Вы должны рассмотреть возможность использования ранжирующих функций. Но в MySQL нет рейтинговой функциональности. Но есть workarounds.

0

Есть несколько предположений, которые необходимо сделать здесь, чтобы двигаться вперед с этим. Я предполагаю, что таблица баллов имеет только одну запись на «имя пользователя», которая как-то эквивалентна псевдониму.

Попробуйте это,

Если бы я имел рабочий дб, это было бы быстро выяснить и испытание, но в основном вы принимаете «под запрос» вы работаете в выбранном поле, и вы строите temp со всеми записями и их фильтрацией.

 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 = ? 

Я не пытался решить вашу логику позже, вы можете использовать ту же теорию, но не стоит и пытаться, если вы не можете подтвердить, что этот метод возвращает правильные строки.

Если вы хотите глубже, вы должны создать некоторые наборы данных и полностью настроить SQL Fiddle.

+0

Спасибо. Я попробую это. «Имя пользователя» используется для адресов электронной почты, которые идентифицируют пользователей и «ник», также уникальны, но могут быть изменены. Да, таблица счетчиков содержит одну запись для каждого пользователя. Меня беспокоит одно: таблица «последние» огромна (200K + строки). Я бы предпочел проверить, идентичны ли имена пользователей вместо псевдонимов ('t.nickname = a.nickname'). Но это нормально. – Z0q

+0

В строке перед последней строкой она останавливается: '# 1054 - Неизвестный столбец 'a.nickname' в 'on clause'. Я не понимаю, как вы пытаетесь объединить и объединить эти два запроса, поэтому я не знаю, как это исправить. Похоже, что MySQL не позволяет использовать поля внутреннего запроса SELECT вне его. – Z0q

+0

На последней строке вместо a.nickname должно быть tmp.nickname, я обновил SQL. Удачи ему –

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