如何结合这两个查询来计算排名变化?

Posted

技术标签:

【中文标题】如何结合这两个查询来计算排名变化?【英文标题】:How do I combine these two queries to calculate rank change? 【发布时间】:2016-05-18 17:34:27 【问题描述】:

简介

我的游戏有一个使用排名的高分表。分数表表示当前的高分和玩家信息,最近的表表示用户最近发布的所有分数,这些分数可能是也可能不是新的最高分数。

排名下降是通过计算玩家的当前排名减去他们在达到最新最高分时的排名来计算的。

排名提升的计算方法是玩家在达到最新最高分时的排名减去他们在达到之前最高分时的排名。

最后,如代码中所写:$change = ($drop > 0 ? -$drop : $increase);

问题

我使用以下两个查询结合一些 php 代码来计算排名变化。它工作得很好,但有时有点慢。

有没有办法优化或组合这两个查询+PHP代码?

我为第一个查询创建了一个 SQL Fiddle: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;

【问题讨论】:

也许切换到 PDO 会更快一些,但它可能不会做任何事情。 能否解释一下: 能否请您定义 1. 这两个表代表什么 & 2. 您的算法/数学用于计算排名?根据我的计算,我的排名是其他人最近得分并且比我在“分数”表中的记录更早(及时)发生的条目数量。加上忽略 !istopscoes @gfunk 1. 分数表代表当前的高分和玩家信息,最近表代表用户最近发布的所有分数,这些分数可能是也可能不是新的最高分。 2.rank drop是通过计算玩家当前的排名减去他们达到最近最高分时的排名。 -- rank increase 是通过计算玩家在达到最新最高分时的排名减去他们在达到上一次最高分时的排名来计算的。最后,如代码所写,$change = $increase - $drop;. 包含样本数据以及预期结果以及对该预期结果的推理(即解释您的术语,如“排名”)可能有助于帮助其他人了解您正在尝试做什么并想出一个答案。 ***.com/help/how-to-ask 【参考方案1】:

如果您将当前的最高分数分离到一个新表中,而所有原始数据都在最近的分数中可用.. 您已经有效地生成了一个汇总表。

为什么不继续总结和总结你需要的所有数据呢?

这只是你知道什么以及什么时候知道的一个例子:

当前排名 - 取决于其他行 新的最高得分排名 - 可以计算为当前排名并在插入/更新时存储 以前的最高分排名 - 当记录新的最高分时,可以从旧的“新最高分排名”转移。

我会更改您的分数表以包含两个新列:

分数 - 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 对任何分数进行排序图表并在显示中包含时间。

【讨论】:

听起来不错的答案。第一部分对我来说完全有意义,但是,Now your queries become a lot simpler 下的第二部分,我不完全理解。你能详细说明一下吗?为什么建议将我的最终排名变化计算从$change = $increase - $drop; 更改为$change = $old_rank_on_update - $rank?它是如何运作的,对玩家来说是否有意义? 要计算一个玩家的当前排名,我相信我需要使用我使用的当前查询,因为它包含了所有的检查,包括时间等。对吗? 抱歉,目前最终计算的其实是$change = ($drop &gt; 0 ? -$drop : $increase); 太好了,谢谢!现在我谈论评论 2 的原因是因为我使用时间来对相等的分数进行排序。因此,如果用户的分数相同,但发布时间较早,则最早发布该分数的用户排名最高。我解释得好吗?【参考方案2】:

我花了很多时间试图弄清楚排名逻辑是什么并对此发表评论。同时,这是一个可以在数据上运行的连接查询 - 我认为您的解决方案会产生这种效果:

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

(请注意,最后一个 AND 只是为了确保您不考虑 r.score==s.score && r.time==s.time - 我猜这将是一个“平局”游戏?)

【讨论】:

感谢您抽出宝贵时间。这个查询的目的是为了计算排名吗?它不起作用。而且,我需要计算排名差异。【参考方案3】:

我不是 MySQL 专家,但我认为在任何 RDBMS 中使用自联接进行排名都是一种不好的做法。您应该考虑使用排名函数。但是 MySQL 中没有排名功能。但是有workarounds。

【讨论】:

【参考方案4】:

为了推进这一点,这里必须做出一些假设。我假设 score 表的每个“用户名”只有一个条目,这在某种程度上相当于一个昵称。

试试这个,

如果我有一个工作数据库,这将很快找出和测试,但基本上你正在使用你在所选字段中运行的“子查询”,并且你正在构建一个包含所有记录的临时表和过滤掉它们。

       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。

【讨论】:

谢谢。我会试试这个。 username 用于标识用户的电子邮件地址,nickname 也是唯一的,但可以更改。是的,分数表包含每个用户一个条目。我担心一件事:recent 表很大(200K+ 行)。我更愿意检查用户名是否相同而不是昵称 (t.nickname = a.nickname)。不过没关系。 在最后一行之前的那一行,停止:#1054 - Unknown column 'a.nickname' in 'on clause' 。我不明白您如何尝试组合和加入这两个查询,所以我不知道如何解决它。似乎 MySQL 不允许内部 SELECT 查询的字段在其外部使用。 在最后一行,应该是 tmp.nickname 而不是 a.nickname 我已经更新了 SQL。祝你好运 它不工作。当我输入我的用户名时,它返回:rank: 0, time: NULL, username: NULL, top: NULL。排名永远不能是0 对不起,我需要示例数据以便进一步研究,也许您可​​以查看我将逻辑从 SELECT 移动到 FROM 的方式,您可以找到一种方法来实现使用您理解的数据模式。根据我的经验,这有助于加快慢速查询。祝你好运

以上是关于如何结合这两个查询来计算排名变化?的主要内容,如果未能解决你的问题,请参考以下文章

SQL工作日查询

我怎样才能将这两个相关的查询结合起来?

结合这两个 mySQL 查询

如何加快我在 sql 中的查询

如何根据两个标准之一检索排名靠前的项目[重复]

Django 结合两个没有 Q 的查询