本文目录导读:

在足球数据分析中,“进攻三区传球成功率”是衡量一支球队在对方半场最靠近球门的区域(即对方禁区前沿30米区域)内,传球的精准度和威胁性/控制力的重要指标。
结合PHP项目(通常涉及后端API、MySQL数据库和前段JS图表),我为你提供一个完整的实现方案,包含数据库设计、核心查询逻辑以及API返回格式。
数据模型设计(MySQL)
传球事件数据存储在事件表中,你需要确保有足够字段来区分比赛、球员、球队和位置。
假设你有以下简化的表结构:
-- 事件表
CREATE TABLE pass_events (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
match_id INT NOT NULL, -- 比赛ID
team_id INT NOT NULL, -- 球队ID(传球方)
player_id INT NOT NULL, -- 球员ID
start_x DECIMAL(5,2), -- 传球起始点X坐标 (0-100)
start_y DECIMAL(5,2), -- 传球起始点Y坐标 (0-100)
end_x DECIMAL(5,2), -- 传球结束点X坐标
end_y DECIMAL(5,2), -- 传球结束点Y坐标
is_accurate TINYINT(1), -- 是否成功 (1=成功, 0=失败)
is_assist TINYINT(1) DEFAULT 0, -- 是否为助攻
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_match_team (match_id, team_id),
INDEX idx_position (start_x, start_y)
);
核心定义:进攻三区判定标准
在标准足球场坐标系中(X 轴为球场长度,Y 轴为宽度,0-100 比例尺):
- 进攻方向:假设你的球队是从左向右进攻(即 X 轴正方向)。
- 进攻三区定义:起始 X 坐标 大于等于 60(即对方半场靠近球门的 1/3 区域)。
注意:如果你的数据是“半场交换制”,或者方向不固定,建议在入库时统一处理为“朝向对方球门”的方向,如果数据源是 Opta 或 StatsBomb,通常有 End X 字段直接指向对方球门。
PHP后端逻辑(Laravel / 原生PHP示例)
我们需要计算传球成功率 = (成功的传球次数 / 总传球次数)* 100。
A. 基础SQL查询(按球队聚合)
<?php
// 假设使用 Laravel Query Builder 或原生 PDO
// 输入参数
$matchId = (int) $_GET['match_id'] ?? 0; // 可选,筛选特定比赛
$teamId = (int) $_GET['team_id'] ?? 0; // 可选,筛选特定球队
$query = "
SELECT
team_id,
COUNT(*) AS total_passes,
SUM(CASE WHEN is_accurate = 1 THEN 1 ELSE 0 END) AS accurate_passes,
ROUND( (SUM(CASE WHEN is_accurate = 1 THEN 1 ELSE 0 END) / COUNT(*)) * 100, 1) AS success_rate
FROM pass_events
WHERE
-- 核心条件:进攻三区(假设己方半场X为0-50,对方半场为50-100)
start_x >= 60
-- 如果需要按比赛筛选
AND (match_id = :match_id OR :match_id = 0)
-- 如果需要按球队筛选
AND (team_id = :team_id OR :team_id = 0)
GROUP BY team_id
ORDER BY success_rate DESC
";
// 执行查询(以 PDO 为例)
$stmt = $pdo->prepare($query);
$stmt->execute([
':match_id' => $matchId,
':team_id' => $teamId
]);
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
// 返回 JSON
header('Content-Type: application/json');
echo json_encode($result);
?>
B. 进阶场景:加入“防线深度”或“压迫”过滤
如果你想看“在对方高位压迫下的传球成功率”,你可能需要结合对手的站位数据,但这通常需要更复杂的地理空间计算。
简化版:只统计传到进攻三区(而不是在进攻三区发起)的成功率
如果你要计算的是“传入进攻三区的成功率”,则将判断条件改为 end_x >= 60:
-- 传入进攻三区
SELECT
team_id,
COUNT(*) AS total_forward_passes,
SUM(is_accurate) AS accurate_passes,
ROUND(AVG(is_accurate) * 100, 1) AS success_rate
FROM pass_events
WHERE end_x >= 60
GROUP BY team_id;
处理“方向”问题(重要)
如果你们的比赛数据是动态翻转的(比如上半场A队从左往右攻,下半场从右往左攻),你必须在数据导入时进行标准化。建议在ETL阶段处理:
// 假设原始数据中,主队默认从左往右攻,客队从右往左攻。
// 如果该传球事件属于客队,则 X 坐标需要翻转。
if ($team_side == 'away') {
// 翻转坐标轴(假设球场长度是100)
$normalized_x = 100 - $original_x;
} else {
$normalized_x = $original_x;
}
// 如果翻转后的X <= 40 (即距离对方球门较近区域),算作进攻三区
API 响应格式建议(给前端图表)
为了让前端(如ECharts、Chart.js)直接使用,建议返回结构化数据:
{
"status": "success",
"data": {
"zone": "进攻三区",
"threshold": "start_x >= 60",
"teams": [
{
"team_id": 1,
"team_name": "曼城",
"total_passes": 120,
"accurate_passes": 102,
"success_rate": 85.0
},
{
"team_id": 2,
"team_name": "利物浦",
"total_passes": 95,
"accurate_passes": 73,
"success_rate": 76.8
}
]
}
}
前端调用(JS示例)
fetch('/api/pass-success?match_id=1001')
.then(response => response.json())
.then(data => {
const chartData = data.data.teams.map(t => ({
name: t.team_name,
value: t.success_rate
}));
// 使用 ECharts 绘制柱状图
const chart = echarts.init(document.getElementById('chart'));
chart.setOption({
xAxis: { type: 'category', data: chartData.map(d => d.name) },
yAxis: { type: 'value', max: 100, name: '成功率(%)' },
series: [{
type: 'bar',
data: chartData.map(d => d.value)
}]
});
});
优化与陷阱提示
- 数据量过大:如果一场比赛传球超5000次,务必给
match_id和start_x建立复合索引(INDEX(match_id, start_x)),否则查询会极慢。 - 准确性:SQL中的
ROUND(..., 1)建议在PHP层做,避免浮点数精度问题。 - 定义歧义:Opta 定义进攻三区为场上 X 坐标 >= 60,但某些统计机构(如 Wyscout)会以“距离球门30米”为界。确保你的数据源定义的坐标系与查询条件一致。
如果你需要更具体的代码(比如原生PHP PDO完整类、或针对特定数据源如StatsBomb的适配),请告诉我你的数据结构,我可以进一步细化。