本文目录导读:

在 PHP 项目中统计头球争顶成功率,核心在于数据建模和计算逻辑,成功率通常定义为:成功次数 / 总争顶次数。
以下是详细的实现方案,涵盖数据库设计、后端计算和前端展示(含示例代码)。
第一步:数据库设计(MySQL / PostgreSQL)
你需要两张核心表:players(球员表)和 events(赛事事件表)。
-
players 表(球员基本信息)
CREATE TABLE players ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL, position VARCHAR(20) ); -
events 表(记录每一次争顶事件) 关键字段是
event_type(事件类型)和is_success(是否成功)。CREATE TABLE match_events ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, match_id INT UNSIGNED NOT NULL, -- 比赛ID player_id INT UNSIGNED NOT NULL, -- 球员ID event_type ENUM('header', 'shot', 'pass') NOT NULL, -- 事件类型 is_success TINYINT(1) NOT NULL DEFAULT 0, -- 1=成功, 0=失败 created_at DATETIME NOT NULL );注意:如果未来要统计“头球射门成功率”和“头球传球成功率”,建议增加一个
sub_type字段,区分clearance(解围)、shot(射门)、assist(助攻)等,也可以用event_name字符串代替 ENUM,便于扩展。
第二步:后端 PHP 计算逻辑(核心)
场景 1:统计单个球员的赛季/累计成功率
<?php
function getPlayerHeaderSuccessRate(PDO $pdo, int $playerId, ?int $seasonId = null): array
{
// 1. 查询基础数据(只统计头球争顶事件)
$sql = "SELECT
SUM(CASE WHEN is_success = 1 THEN 1 ELSE 0 END) AS success_count,
COUNT(*) AS total_count
FROM match_events e
INNER JOIN matches m ON e.match_id = m.id
WHERE e.player_id = :player_id
AND e.event_type = 'header'
AND (:season_id IS NULL OR m.season_id = :season_id)";
$stmt = $pdo->prepare($sql);
$stmt->execute([
':player_id' => $playerId,
':season_id' => $seasonId
]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
$total = (int)$result['total_count'];
$success = (int)$result['success_count'];
// 2. 计算成功率(避免除零错误)
$rate = 0;
if ($total > 0) {
$rate = round(($success / $total) * 100, 1); // 保留1位小数
}
return [
'player_id' => $playerId,
'total_attempts'=> $total,
'success_count' => $success,
'failure_count' => $total - $success,
'success_rate' => $rate, // 65.5
];
}
// 使用示例
$pdo = new PDO('mysql:host=localhost;dbname=football_stats', 'user', 'pass');
$stats = getPlayerHeaderSuccessRate($pdo, 101, 2024);
print_r($stats);
/*
输出:
Array
(
[player_id] => 101
[total_attempts] => 50
[success_count] => 33
[failure_count] => 17
[success_rate] => 66
)
*/
?>
场景 2:统计全队所有球员的排行榜(展示用)
<?php
function getTeamHeaderStats(PDO $pdo, int $teamId, int $matchRange = 10): array
{
// 获取最近 N 场比赛(通过 match_id 关联)
$sql = "SELECT
p.id, p.name,
SUM(CASE WHEN e.is_success = 1 THEN 1 ELSE 0 END) AS success_count,
COUNT(e.id) AS total_count
FROM match_events e
INNER JOIN players p ON e.player_id = p.id
INNER JOIN matches m ON e.match_id = m.id
WHERE m.home_team_id = :team_id OR m.away_team_id = :team_id
AND e.event_type = 'header'
AND m.match_date >= DATE_SUB(NOW(), INTERVAL :range DAY) -- 或者限定赛季
GROUP BY p.id, p.name
ORDER BY total_count DESC, success_count DESC
LIMIT 20";
$stmt = $pdo->prepare($sql);
$stmt->execute([':team_id' => $teamId, ':range' => $matchRange]);
$players = $stmt->fetchAll(PDO::FETCH_ASSOC);
// 计算百分比并格式化
foreach ($players as &$player) {
$player['success_rate'] = $player['total_count'] > 0
? round(($player['success_count'] / $player['total_count']) * 100, 1)
: 0;
}
return $players;
}
?>
第三步:前端展示(HTML + 简易 CSS)
前端主要是格式化显示,重点突出“成功率”数字。
<!DOCTYPE html>
<html>
<head>
<style>
.stat-table { border-collapse: collapse; width: 100%; }
.stat-table th, .stat-table td { border: 1px solid #ddd; padding: 8px; text-align: center; }
.stat-table th { background-color: #f2f2f2; }
.rate-good { color: green; font-weight: bold; } <!-- 高成功率 -->
.rate-mid { color: orange; } <!-- 中成功率 -->
.rate-low { color: red; } <!-- 低成功率 -->
</style>
</head>
<body>
<h3>队内头球争顶成功率排行榜</h3>
<table class="stat-table">
<thead>
<tr>
<th>排名</th>
<th>球员</th>
<th>总争顶次数</th>
<th>成功次数</th>
<th>失败次数</th>
<th>成功率</th>
</tr>
</thead>
<tbody>
<?php
// 假设通过 API 或直接调用了上面的 getTeamHeaderStats() 函数
$stats = getTeamHeaderStats($pdo, 1, 30);
$rank = 1;
foreach ($stats as $player):
// 根据成功率设置CSS类
$rateClass = 'rate-low';
if ($player['success_rate'] >= 60) $rateClass = 'rate-good';
elseif ($player['success_rate'] >= 40) $rateClass = 'rate-mid';
?>
<tr>
<td><?php echo $rank++; ?></td>
<td><?php echo htmlspecialchars($player['name']); ?></td>
<td><?php echo $player['total_count']; ?></td>
<td><?php echo $player['success_count']; ?></td>
<td><?php echo $player['total_count'] - $player['success_count']; ?></td>
<td class="<?php echo $rateClass; ?>">
<?php echo $player['success_rate']; ?>%
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</body>
</html>
进阶优化建议
缓存计算(性能优化)
如果数据量大,不要每次请求都跑 COUNT 和 SUM,可以建一张 player_stats_cache 表,定期更新。
CREATE TABLE player_stats_cache (
player_id INT PRIMARY KEY,
total_headers INT DEFAULT 0,
successful_headers INT DEFAULT 0,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
区分“头球争顶”和“头球射门”
如果产品需求要分别统计“防守解围头球”和“进攻射门头球”,需要修改 event_type 或增加 sub_type 字段。
定义“成功”的判定标准 这是统计的核心难点,需要明确:
- 成功:球员用头将球顶向本方/对方球门方向、或者稳妥解围、或者传给队友形成有效控制。
- 失败:顶球出界、顶给对方、或者根本没顶到(冒顶)。
建议:在事件录入页面,由数据录入员根据录像回放手动标记 is_success,这是最准确的。
跨赛季统计
在查询时通过 INNER JOIN matches 关联 season_id 字段进行过滤即可,参数化查询可以支持灵活筛选。
这个功能的核心不是 PHP 语法,而是明确统计口径,推荐使用单项事件(每次争顶一条记录)来存储,通过简单的 COUNT 和 SUM 聚合,就能得到准确数据,且方便扩展到助攻、拦截等其他数据项。