2013-02-11 68 views
0

我有下面的表:match_id,player_slot,能力,水平MySQL查询:简化子查询和联盟

有每场比赛10个播放器插槽,每个英雄最多可以有25级和五分之一能力。我从API中提取数据,并且需要快速提取数据,所以我没有太多灵活性来设置我的表。

但是,为了显示数据,我真的很努力地将现有的表格格式变成可行的东西。对于每场比赛,我想已经制定了这样的数据:

球员SLOT1,在1水平的能力,在2水平的能力,......,有能力在16级

...

球员时隙10,在1水平的能力,在2级能力,...,16级

我能得到一个工作示例(Here's an example)了,但它是一个非常丑陋的查询能力。对于每一行我有15个嵌套的子查询,然后执行一个联合来加入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 

我怎么会去简化呢?我应该以不同的方式呈现这些数据来制作视图或新表格吗?同样复杂的是,每个玩家都会在不同的级别结束游戏。我现在所做的工作,但似乎必须有一个更有效的方式来运行查询。我尝试了一些不同的东西,但似乎无法使我的其他查询的行合并正确。

回答

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子句在player_slot之前包括match_id列(以及将它包括在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值。 ,那么MAX聚集函数将在一个a.ability值和一堆NULL上运行,在更一般的情况下,如果我们没有保证是唯一的,那么MAX汇总可能会在两个(或更多)非NULL a.ability值。在更一般的情况下,如果您想在每个级别返回一个简短的a.ability(作为字符串)列表,那么GROUP_CONCAT聚合函数可能会有用(代替MAX)

+0

我会删除并调整player_slot ... – 2013-02-11 21:04:25

+0

@Nitin Midha:是的,可以从查询中删除player_slot(> = 1和<= 10)上的谓词;但从OP不清楚10个播放器插槽的限制是否由查询执行,或者这是否是底层数据的限制。 – spencer7593 2013-02-11 21:06:42

+0

谢谢,这个伎俩。 10个播放器插槽的限制是底层数据的一个限制,所以它在没有WHERE子句中的a.player_slot项目的情况下工作正常 – user1723535 2013-02-12 15:30:04

0

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

+0

我确实有其他的表,比赛,球员等c。将api_ability表分解成多个部分似乎有点过分,因为这些点本身并不能传达很多信息。没有关卡的能力不会告诉我任何事情,如果没有match_id和player_slot,那么行就不会是唯一的。我想我可以将这张表分成一堆子表,并将它们与一个构建的关键字(一个单独的表,只是具有构造的关键字和关卡等)关联起来,但这真的是首选吗?加上多个插入可能会伤害我的API脚本运行时间。 – user1723535 2013-02-12 15:42:23

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

我会删除并在player_slot条件... – 2013-02-11 21:03:40

+0

@NitinMidha为什么?在原始查询中,他们使用'match_id'和'player_slot'。删除该连接条件可能不正确。 OP将不得不进一步澄清他们正在尝试做什么。 – Taryn 2013-02-11 21:05:10

+0

按照他的问题“对于每一行,我有15个嵌套子查询,然后执行一个联合来加入10行中的每一行。”他显示的片段重复从1到10 player_slot,所以我认为你的答案满足他的所有需求... – 2013-02-11 21:09:51