计算第3个NF

时间:2015-04-22 09:16:01

标签: php mysql database database-normalization calculated-columns

我有一个数据库,其中存储了射击游戏的结果。我把它们放到3NF以允许扩展系统。所以它看起来像这样:

Player
-------------------
GameId integer
PlayerId integer
TeamId integer

Hits
-------------------
GameId integer 
FromId integer
ToId integer
Hits integer

所以基本上每个游戏都有一个ID,每个玩家和团队都有自己的ID(其名称存储在其他数据库中)

现在我想计算每个玩家的积分。我需要每场比赛的积分,但更重要的是每位玩家的积分。积分基本上是:每次击中对手得3分,团队成员每次命中-2分,每次击中得-2分。 单独计算团队命中数需要一个包含3个表的JOIN,我担心在生产环境中的性能。 (每个游戏有~8个玩家 - > PlayerDB-Size是8n而HitsDB-Size是(8-1)^ 2 * n)

最后:我需要计算每个游戏的每个玩家的积分并将其总结,因为每场比赛的最低积分应为零。最后得到每个玩家的等级(玩家x的总积分排名第二等)

我觉得我迷失在过于复杂的查询中,这些查询会在某些时候破坏数据库的性能。

任何人都可以判断设计并给我一些指示从哪里开始进一步观察?我虽然关于在玩家数据库中存储TeamHits和每场比赛积分(总结它们的点,团队命中用于统计目的),但这当然会打破正常化。

PS:我正在使用PHP 5和MYSQL。我还想过从数据库中获取每个游戏,计算PHP中的点数(我在展示游戏时已经在做的事情)并将其写回(最好是将游戏放入数据库中,但也可以参考分数变化)

编辑:避免子选择的想法是:

SELECT p.*, SUM(h.Hits) AS TeamHits, SUM(h2.Hits) as Hits
FROM player p
LEFT JOIN
  (hits h
    INNER JOIN player p2
    ON h.GameId=p2.GameId AND h.ToId=p2.PlayerId
  )
ON p.GameId=p2.GameId AND h.FromId=p.PlayerId AND p.TeamId=p2.TeamId
GROUP BY p.PlayerId, p.GameId
LEFT JOIN hits h2
ON h2.GameId=p.GameId AND h2.FromId=p.PlayerId

但当然这不起作用。甚至可以将分组与连接组合在一起,还是必须使用子查询? 我最好的是:

SELECT p.PlayerId, SUM((-2-3)*IFNULL(th.TeamHits, 0) + (3)*IFNULL(h.Hits, 0) + (-2)*IFNULL(ht.HitsTaken, 0)) AS Points
FROM player p
LEFT JOIN
    (SELECT p.GameId, p.PlayerId, SUM(h.Hits) AS TeamHits
    FROM player p
    INNER JOIN hits h
    ON h.GameId=p.GameId AND p.PlayerId=h.FromId
    INNER JOIN player p2
    ON p.GameId=p2.GameId AND p2.PlayerId=h.ToId AND p.TeamId=p2.TeamId
    GROUP BY p.PlayerId, p.GameId) th
ON p.GameId=th.GameId AND p.PlayerId=th.PlayerId
LEFT JOIN
    (SELECT p.GameId, p.PlayerId, SUM(h.Hits) AS Hits
    FROM player p
    INNER JOIN hits h
    ON h.GameId=p.GameId AND p.PlayerId=h.FromId
    GROUP BY p.PlayerId, p.GameId) h
ON p.GameId=h.GameId AND p.PlayerId=h.PlayerId
LEFT JOIN
    (SELECT p.GameId, p.PlayerId, SUM(h.Hits) AS HitsTaken
    FROM player p
    INNER JOIN hits h
    ON h.GameId=p.GameId AND p.PlayerId=h.ToId
    INNER JOIN player p2
    ON p.GameId=p2.GameId AND p2.PlayerId=h.FromId AND p.TeamId!=p2.TeamId
    GROUP BY p.PlayerId, p.GameId) ht
ON p.GameId=ht.GameId AND p.PlayerId=ht.PlayerId
GROUP BY p.PlayerId

小提琴:http://sqlfiddle.com/#!9/dc0cb/4

当前问题:对于拥有大约10,000个游戏的数据库,计算所有玩家的积分需要大约18秒。这是无法使用的,所以我需要改进这个......

1 个答案:

答案 0 :(得分:0)

连接不是那么昂贵,子查询是。只要你能避免使用子查询就不会太糟糕。

请记住,最近为这些东西构建了一个数据库。 只需确保您在正确的字段上有正确的索引,以便进行优化。像teamID和GameID一样,playerID应该是索引。

只需在phpmyadmin中运行它,看看执行它需要多少毫秒。如果它需要超过50个重要的查询,但通常很难达到这个...我曾经设法做出一个非常繁重的查询,从不同的表和视图中加入100.000+行,并且仍然在5ms内完成。

我们在谈论一小时的请求数量是多少?每天200名球员?每天200,000名球员?请求多久发生一次?每位玩家每秒10张?一分钟一次?你的数据库加载了多少?

我认为所有这些参数都很低,所以你不必担心这种优化。 让您的游戏启动并运行,清理可以获得真正收益的​​PHP代码,并避开复杂的子查询或视图。

只要你的桌子加入和加入它的速度非常快。如果你必须做一个子查询,通过使用链接表将某些结果链接到某些其他表来查看是否没有其他方法,这样你就可以进行连接而不是子查询。