0

我有一个包含以下列的表:match_id、player_slot、能力、级别

每场比赛有 10 个玩家位置,每个英雄最多可以有 25 个级别和五种能力之一。我正在从 API 中提取数据,并且需要快速拉取数据,因此我对表格的设置方式没有太大的灵活性。

然而,为了显示数据,我真的很难将现有的表格格式变成可行的东西。对于每场比赛,我希望数据的布局如下:

玩家 slot1, 1 级能力, 2 级能力, ..., 16 级能力

...

玩家 slot10, 1 级能力, 2 级能力, ..., 16 级能力

我可以得到一个工作示例(Here's an example),但这是一个非常丑陋的查询。对于每一行,我有 15 个嵌套子查询,然后执行一个 UNION 来连接 10 行中的每一行。

查询片段:

SELECT player_slot, ability,
(SELECT ability FROM api_abilities WHERE match_id = 119009486 AND player_slot = 1 AND level = 2 ),
...
(SELECT ability FROM api_abilities WHERE match_id = 119009486 AND player_slot = 1 AND level = 16 )
FROM api_abilities
WHERE match_id = 119009486 AND player_slot = 1 AND level = 1

我将如何简化这一点?我应该使用以不同方式呈现的这些数据创建视图或新表吗?同样复杂的是,每个玩家都会以不同的级别结束游戏。我现在所拥有的确实有效,但似乎必须有一种更有效的方式来运行查询。我尝试了一些不同的方法,但似乎无法在我的其他查询中正确地进行行合并。

4

3 回答 3

1

这种类型的转变是一个支点。不幸的是,MySQL 没有数据透视函数,因此您需要使用多个连接或聚合函数来复制它。

您可以使用多个连接而不是子查询:

SELECT 
  l1.player_slot, 
  l1.ability Level1Ability,
  l2.ability Level2Ability,
  l16.ability Level16Ability
FROM api_abilities l1
LEFT JOIN api_abilities l2
  on l1.match_id = l2.match_id 
  and l1.player_slot = l2.player_slot
  and l2.level = 2
-- .... add more joins for each level
LEFT JOIN api_abilities l16
  on l1.match_id = l16.match_id 
  and l1.player_slot = l16.player_slot
  and l16.level = 16
WHERE l1.match_id = 119009486 
  AND l1.player_slot >= 1
  AND l1.player_slot <= 10
  AND l1.level = 1

或者您可以使用带有CASE表达式的聚合函数:

select
  player_slot,
  max(case when level = 1 then ability end) Level1Ability,
  max(case when level = 2 then ability end) Level2Ability,
  -- ... add more case expressions for each level
FROM api_abilities 
where match_id = 119009486 
  and player_slot >= 1
  and player_slot <= 10
group by player_shot
于 2013-02-11T21:00:03.403 回答
1

这是一种方法:

SELECT a.player_slot
     , MAX(IF(a.level=1,a.ability,NULL)) AS ability_at_level_1
     , MAX(IF(a.level=2,a.ability,NULL)) AS ability_at_level_2
     , MAX(IF(a.level=3,a.ability,NULL)) AS ability_at_level_3
     , MAX(IF(a.level=4,a.ability,NULL)) AS ability_at_level_4
     , MAX(IF(a.level=5,a.ability,NULL)) AS ability_at_level_5
   ...   
     , MAX(IF(a.level=15,a.ability,NULL)) AS ability_at_level_15
     , MAX(IF(a.level=16,a.ability,NULL)) AS ability_at_level_16
  FROM api_abilities a
WHERE a.match_id = 119009486
  AND a.player_slot >= 1
  AND a.player_slot <= 10
GROUP
   BY a.player_slot

如果您希望查询返回多个match_id, 您会希望GROUP BY子句包含match_id之前的列player_slot, (以及将其包含在 SELECT 列表中。

GROUP
   BY a.match_id
    , a.player_slot

为了获得最佳性能,您需要一个合适的索引。覆盖索引将(可能)提供最佳查询性能:

ON api_abilities (match_id, player_slot, level, ability)

这是一个合适的索引的原因是因为您在列上有一个相等谓词,在match_id列上有一个范围谓词和一个 GROUP BY player_slot。(即使删除了 player_slot 上的范围谓词,此索引仍然适用。)levelability列的包含不是绝对必要的,但包含它们会使索引成为“覆盖索引”。之所以称为“覆盖索引”,是因为可以完全从索引中满足查询(EXPLAIN 输出将显示“使用索引”),而无需访问索引下的数据页面。

MAX 聚合和 IF 函数的使用使我们能够从适当的行中“挑选”出 a.ability。(“诀窍”是对于任何不在指定级别的行,IF 函数都返回 NULL。我们对 MAX 所做的实际操作是丢弃那些 NULL 值。

如果(match_id, player_slot, level)是唯一的,那么 MAX 聚合函数将对一个a.ability值和一堆 NULL 进行操作。在更一般的情况下,如果我们不能保证它是唯一的,那么 MAX 聚合可能会在两个(或更多)非 NULLa.ability值上运行。a.ability在更一般的情况下,如果您想在每个级别返回(作为字符串)的简短列表,则 GROUP_CONCAT 聚合函数可能有用(代替 MAX) 。

于 2013-02-11T21:00:26.883 回答
0

在我看来,您需要更好地了解数据库规范化。根据您的描述,我仍然不确定您在此处尝试做什么,但我似乎很清楚,这些数据应该在多个表中表示(一张用于比赛,一张用于玩家,一张用于能力,一张用于相关玩家能力和能力水平,将球员与比赛联系起来,等等)。

于 2013-02-11T20:59:25.917 回答