2013-02-11 66 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