Как посчитать показатель побед связки героев в DotA2

Под связкой понимается комбинация героев, заигранная за одну (radiant) или другую (dire) сторону в матче.

Изначально есть БД стандартной структуры, заполненная где-то на 10,000 матчей. Необходимо сделать возможность посчитать «винрейт» любой связки героев (до 5 включительно) против другой связки (так же до 5 включительно). Необходимо учесть, что винрейт может быть как против другой связки, так и «против всех». Еще одно условие — выборка должна содержать данные, отвечающие стороне (dire/radiant), за которую была использована связка.

Для тех, кому интересен только результат — ссылка.

Вначале рассмотрим ситуацию, когда «одна связка против всех». SQL-запрос выглядит так:

SELECT 
	m.match_id, m.radiant_win, m.radiant_name, m.dire_name, 1 AS team, m.start_time 
FROM 
	matches AS m 
JOIN (
	SELECT 
		match_id 
	FROM 
		slots
	WHERE 
		(player_slot < 5 AND hero_id IN ('.$s_team1.')) 
	GROUP BY 
		match_id 
	HAVING 
		COUNT(*) = '.count($team1).')
AS 
	k1 
ON 
	k1.match_id = m.match_id
UNION
SELECT 
	m.match_id, m.radiant_win, m.radiant_name, m.dire_name, 0 AS team, m.start_time 
FROM 
	matches AS m
JOIN (
	SELECT 
		match_id 
	FROM 
		slots 
	WHERE 
		(player_slot > 126 AND hero_id IN ('.$s_team1.'))
	GROUP BY 
		match_id 
	HAVING 
		COUNT(*) = '.count($team1).') 
AS 
	k2 
ON 
	k2.match_id = m.match_id

$team1 — это массив с id-шниками героев связки, а $s_team1 — это строка, где через запятую перечислены эти же id. Почему в запросе UNION? Потому что связка героев может быть как за одну, так и за другую сторону (не в одном матче, а вообще).

Что такое «1 AS team» и «0 AS team«? Так как надо посчитать именно винрейт связки, а не просто найти все матчи, где она есть, то необходимо помимо radiant_win (поле указывающие, какая из сторон победила) включить в выборку еще и данные, за какую сторону играла связка (1 — radiant, 0 — dire). Тогда победа связки будет при условии «radiant_win === team» (это уже считается не в sql, а коде, который обрабатывает полученные от этого запроса данные).

Как понять, за какую сторону идет выборка? Часть условия с player_slot отвечает за это (radiant — < 5, dire — > 126).

Что такое HAVING в каждом из подзапросов? Стоит напомнить про структуру самой БД. Данные про матч — одна таблица (команды, лига, время начала, победитель и т.д), а данные по игрокам в матче (по слотам) — это другая таблица. Получается, что для матча есть одна строчка из таблицы matches и 10 строк из slots (по 5 на каждую сторону). Связка включает в от 2 до 5 героев. И не известно, кто на каком слоте мог быть. «HAVING COUNT..» как раз помогает определить количество совпадений по героям связки. И это количество должно равняться количеству героев в связке.

Ситуация, когда «связка на связку» немного отличается и содержит несколько подводных камней. Для начала выберем матчи, где была использована первая связка. При чем матчи выберем отдельно для radiant и dire:

/* $team1_radiant_matches */
SELECT 
	match_id 
FROM 
	slots 
WHERE 
	(player_slot < 5 AND hero_id IN ('.$s_team1.')) 
GROUP BY 
	match_id 
HAVING 
	COUNT(*) = '.count($team1).'
/* $team1_dire_matches */
SELECT 
	match_id 
FROM 
	slots 
WHERE 
	(player_slot > 126 AND hero_id IN ('.$s_team1.')) 
GROUP BY 
	match_id 
HAVING 
	COUNT(*) = '.count($team1).'

Как и в предыдущем случае, $team1 — это массив id-шников первой связки, а $_steam1, это строка, где эти id-шники перечислены через запятую.

Теперь можно сказать, что задача свелась к случаю, когда есть одна связка, но выборка уже идет не со всех матчей, а с тех, где была первая связка:

SELECT 
	m.match_id, m.radiant_win, m.radiant_name, m.dire_name, 1 AS team, m.start_time 
FROM 
	matches AS m 
JOIN (
	SELECT 
		match_id 
	FROM 
		slots 
	WHERE 
		(player_slot > 126 AND hero_id IN ('.$s_team2.')) AND 
		match_id IN ('.implode(',', $team1_radiant_matches).') 
	GROUP BY 
		match_id 
	HAVING 
		COUNT(*) = '.count($team2).') AS k1 
ON 
	k1.match_id = m.match_id
UNION
SELECT 
	m.match_id, m.radiant_win, m.radiant_name, m.dire_name, 0 AS team, m.start_time 
FROM 
	matches AS m 
JOIN (
	SELECT 
		match_id 
	FROM 
		slots 
	WHERE 
		(player_slot < 5 AND hero_id IN ('.$s_team2.')) AND 
		match_id IN ('.implode(',', $team1_dire_matches).') 
	GROUP BY 
		match_id 
	HAVING 
		COUNT(*) = '.count($team2).') AS k2 
ON 
	k2.match_id = m.match_id

$team2 — вторая связка (массив id-шников героев). $s_team2 — строка, где id-шники связки идут через запятую.

Зачем три запроса, если можно сделать один? Можно, если первых два запроса поставить на место $team1_radiant_matches и $team1_dire_matches в третьем запросе, то он будет работать в сотни(!) раз медленнее. На картинках ниже показано DESCRIBE (картинки кликабельные). Первый — для запроса «три в одном», а второй — для запроса, где $team1_radiant_matches и $team1_dire_matches являются строками вида «1,2,4,6,8…». Количество лишних «телодвижений» в первом случае просто огромное:

slow request

fast request

Пример того, как это все может работать доступен по ссылке — HeroVsHero.

, , ,

3 комментария
  1. DaoTao сказал(а):

    Спасибо за статью! Можно с вами как то связаться? Или напишите мне на почту.

  2. KronuS сказал(а):

    DaoTao, пожалуйста.
    На счет связи — коммерция, связанная с Dota2, меня не интересует (помочь ничем не смогу). Какие-либо вопросы по API задавайте через github — https://github.com/kronusme/dota2-api/issues.

  3. Dark Koder сказал(а):

    Кронус, у тебя там бага есть. стукни в жабу, я отпишу линк и скрин страницы

Оставить комментарий

Top ↑ | Main page | Back