MySQL查询:简化子查询和联盟

时间:2013-02-11 20:50:04

标签: mysql

我有一个包含以下列的表:match_id,player_slot,ability,level

每场比赛有10个玩家老虎机,每个英雄最多可以有25个等级和5个异能中的一个。我从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

我如何简化这个?我应该以不同的方式提供这些数据的视图或新表吗?同样复杂的是,每个玩家将在不同级别结束游戏。我现在所做的工作,但似乎必须有一种更有效的方式来运行查询。我尝试了一些不同的东西,但似乎无法在我的其他查询中纠正行合并。

3 个答案:

答案 0 :(得分:1)

此类转换是 pivot 。不幸的是,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

答案 1 :(得分: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列上有一个等式谓词,player_slot列上有一个范围谓词和GROUP BY。 (即使删除了player_slot上的范围谓词,此索引仍然适用。)包含levelability列并非绝对必要,但包含它们会使索引成为“覆盖索引” 。它被称为“覆盖索引”,因为查询可以完全从索引中获得(EXPLAIN输出将显示“使用索引”),而无需访问索引下的数据页。

MAX聚合和IF函数的使用使我们能够从适当的行中“挑选”出来。 (“技巧”是对于任何不在指定级别的行,IF函数返回NULL。我们对MAX的实际做法是抛出那些NULL值。

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

答案 2 :(得分:0)

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