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

Php 如何结合这两个查询来计算排名变化?,php,mysql,sql,mysqli,combinations,Php,Mysql,Sql,Mysqli,Combinations,导言 我有一个使用等级的高分表。分数表表示当前的高分和球员信息,最近的表表示用户最近发布的所有分数,这些分数可能是或可能不是新的高分 排名下降是通过计算球员的当前排名减去他们在达到最新最高得分时的排名来计算的 排名增加是通过计算球员在达到最近的最高得分时的排名减去他们在达到之前的最高得分时的排名来计算的 最后,如代码所述:$change=$drop>0-$下降:$增加 问题: 我将使用以下两个查询和一些PHP代码来计算排名变化。它工作得非常好,但有时有点慢 有没有办法优化或组合这两个查询+PH

导言

我有一个使用等级的高分表。分数表表示当前的高分和球员信息,最近的表表示用户最近发布的所有分数,这些分数可能是或可能不是新的高分

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

排名增加是通过计算球员在达到最近的最高得分时的排名减去他们在达到之前的最高得分时的排名来计算的

最后,如代码所述:$change=$drop>0-$下降:$增加

问题:

我将使用以下两个查询和一些PHP代码来计算排名变化。它工作得非常好,但有时有点慢

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

我创建了第一个查询的SQL FIDLE:

这些表格已经充满了内容,所以它们的结构不应该改变

这是当前的工作代码:

$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;

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

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-我想这将是一场平局游戏?

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

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

试试这个

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

       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 FIDLE

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

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

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

当前排名-取决于其他行 新最高分数排名-可作为当前排名计算,并在插入/更新时存储 以前的最高得分排名-记录新的最高得分时,可以从旧的“新最高得分排名”转移。 我将更改您的分数表,使其包含两个新列:

分数-id、分数、用户名、昵称、时间、更新时的排名、更新时的旧排名 并在更新/插入每行时调整这些列。 看起来您已经有了可用于在第一次迭代中反写此数据的查询

现在您的查询变得简单多了

要从分数中获得排名,请执行以下操作:

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
但我至少会尝试一下union的性能:

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
一个关于分数的索引,时间在这里会有所帮助


就我个人而言,我会让自己省去一点头疼,并保持同样的分数,保持同样的等级,我相信这是相当标准的。。如果你想让人们拥有首次吹牛的权利,只需确保你在任何分数表上按时间ASC排序,并将时间包含在显示中。

切换到PDO可能会加快速度,但可能不会起任何作用。请你解释一下:你能定义1吗。这两张表所代表的是&2。计算排名的算法/数学是什么?根据我的计算,我的排名是其他人最近的条目中得分较高且出现时间早于我在分数表中的记录的数量。加上忽略!istopscoes@gfunk1.分数表表示当前的高分和球员信息以及最近的成绩
表表示用户最近发布的所有分数,这些分数可能是或可能不是新的最高分数。2.排名下降是通过计算球员的当前排名减去他们在达到最新最高得分时的排名来计算的。-排名增加是通过计算球员在达到最近的最高得分时的排名减去他们在达到之前的最高得分时的排名来计算的。最后,如代码所述,$change=$increase-$drop;。包括样本数据以及预期结果和预期结果的理由,即解释你的排名等术语,可能会大大有助于帮助其他人了解你在尝试做什么,并给出答案。谢谢你抽出时间。此查询的目的是计算排名吗?它不起作用。还有,我需要计算排名差异。谢谢。我试试这个。用户名用于标识用户的电子邮件地址,昵称也是唯一的,但可以更改。是的,分数表包含每个用户一个条目。有一件事我很担心:最近的表有20多万行。我更愿意检查用户名是否相同,而不是昵称t.nickname=a.nickname。但这没关系,在最后一行之前的那一行,它停止了:1054-未知列“On子句”中的“a.nickname”。我不明白您是如何尝试合并和连接这两个查询的,所以我不知道如何修复它。MySQL似乎不允许在外部使用内部SELECT查询的字段。在最后一行,它应该是tmp.nickname,而不是a.nickname,我已经更新了SQL。祝你好运,它不起作用了。当我输入用户名时,它返回:秩:0,时间:NULL,用户名:NULL,top:NULL。秩永远不能为0。对不起,为了进一步研究,我需要示例数据,也许您可以查看一下我将逻辑从SELECT移动到from的方式,您可以找到一种方法,使其与您理解的数据架构一起工作。根据我的经验,这有助于加快缓慢的查询速度。好运气就像一个很好的答案。第一部分对我来说完全有意义,但是,第二部分现在你的查询变得简单多了,我不完全理解。你能详细说明一下吗?为什么你会建议将我的最终排名变化计算从$change=$increase-$drop;to$change=$old\u rank\u on\u update-$rank?它是如何工作并对玩家有意义的?要计算玩家的当前排名,我相信我需要使用我使用的当前查询,因为它包含所有检查,包括时间等。对吗?对不起,最终的计算实际上是$change=$drop>0-$下降:$增加;。太好了,谢谢!现在我谈论评论2的原因是因为我用时间对相等的分数进行排序。因此,如果一个用户拥有相同的分数,但是发布得更早,那么最早发布该分数的用户将拥有最高的排名。我解释得好吗?