本文目录导读:

在 PHP 项目中统计赛季累计数据并进行对比,是一个很常见的需求(比如体育赛事、游戏排位、销售季度等),要做得高效且灵活,关键在于数据库设计和查询策略,而不是在 PHP 代码里写一堆循环。
下面我整理了一套从架构设计到代码实现的完整方案,并附上高性能的写法。
数据库设计建议(核心!)
避免每次统计都把全表数据捞到 PHP 里算,最好在数据库层面完成聚合。
-- 示例:球员得分表
CREATE TABLE game_stats (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
player_id INT NOT NULL,
season VARCHAR(10) NOT NULL, -- 赛季标识,如 '2024-2025'
points INT DEFAULT 0,
rebounds INT DEFAULT 0,
assists INT DEFAULT 0,
game_date DATE NOT NULL,
INDEX idx_player_season (player_id, season) -- 复合索引,必须要加!
);
反例(不要这么做):
// ❌ 致命错误:把所有赛季数据查出来,用PHP数组求和
$rows = $db->query("SELECT * FROM game_stats WHERE player_id = 1");
$total_points = 0;
foreach ($rows as $row) {
if ($row['season'] == '2023') { $total_points += $row['points']; }
}
高效 SQL 统计(秒级响应)
使用 GROUP BY 对多个赛季进行分组对比,一次查询搞定所有对比数据。
<?php
// 假设 $pdo 是 PDO 实例
$sql = "SELECT
season,
COUNT(*) AS games_played,
SUM(points) AS total_points,
AVG(points) AS avg_points,
SUM(rebounds) AS total_rebounds,
MAX(points) AS season_high
FROM game_stats
WHERE player_id = :player_id
AND season IN (:season1, :season2) -- 对比当前季和上季
GROUP BY season
ORDER BY season";
$stmt = $pdo->prepare($sql);
$stmt->execute([
':player_id' => $playerId,
':season1' => $currentSeason,
':season2' => $previousSeason
]);
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
// 重新整理数据,方便前端直接做对比
$comparison = [
'current' => [],
'previous' => []
];
foreach ($results as $row) {
if ($row['season'] === $currentSeason) {
$comparison['current'] = $row;
} elseif ($row['season'] === $previousSeason) {
$comparison['previous'] = $row;
}
}
常用对比逻辑与计算
拿到两组数据后,通常只需要在 PHP 里做简单的算术运算(这没有性能问题)。
<?php
// 计算各项指标的同比变化率
function calculateChange($current, $previous) {
if ($previous == 0) {
return $current > 0 ? 100 : 0; // 防止除零
}
return round((($current - $previous) / $previous) * 100, 2);
}
// 假设从数据库拿到了两组数据
$current = $comparison['current'] ?? [];
$previous = $comparison['previous'] ?? [];
// 直接对比输出
echo "本赛季总得分: " . ($current['total_points'] ?? 0) . " | 上赛季: " . ($previous['total_points'] ?? 0);
echo "得分涨幅: " . calculateChange($current['total_points'] ?? 0, $previous['total_points'] ?? 0) . "%";
进阶:跨赛季走势图(线性对比)
如果需要生成逐轮/逐月的累计变化曲线(比如显示第5轮时,本赛季累计得分 vs 上赛季累计得分),推荐使用 窗口函数(MySQL 8.0+) 或 子查询。
MySQL 窗口函数(最高效)
SELECT
game_date,
season,
SUM(points) OVER(PARTITION BY season ORDER BY game_date) AS cumulative_points
FROM game_stats
WHERE player_id = :player_id
AND season IN ('2024', '2023')
ORDER BY game_date;
PHP 端循环累加(适用于老版本 MySQL)
$data = []; // [date => ['current' => X, 'previous' => Y]]
$current_total = 0;
$previous_total = 0;
foreach ($rawRows as $row) {
if ($row['season'] == '2024') {
$current_total += $row['points'];
$data[$row['game_date']]['current'] = $current_total;
} else {
$previous_total += $row['points'];
$data[$row['game_date']]['previous'] = $previous_total;
}
}
// 然后按日期合并,注意对齐日期(有些日期只打了比赛,有些没打)
性能优化大坑提醒
- 索引必须建:
(player_id, season)索引是必须的,否则数据量一大,全表扫描会让页面崩溃。 - 缓存结果:赛季累计数据通常是只读不频繁修改的,使用 Redis 或 MEMCACHED 缓存计算结果。
$cacheKey = "season_stats_{$playerId}_{$season}"; $stats = $redis->get($cacheKey); if (!$stats) { // 查数据库,计算好后写入 Redis,设置过期时间 10 分钟或比赛结束自动失效 $redis->setex($cacheKey, 600, json_encode($stats)); } - *避免 `SELECT
**:只查你需要的字段(pointsseasonrebounds`)。 - 分页问题:如果是全量球员排行对比排行榜,千万不要把每个球员的统计都查出来再排序,使用 SQL 直接排序加 LIMIT:
SELECT player_id, SUM(points) as total FROM game_stats WHERE season = '2024' GROUP BY player_id ORDER BY total DESC -- 直接在SQL里完成降序 LIMIT 100;
代码架构(简单分层)
如果你的项目是原生 PHP 或轻量框架,建议抽个类来管理:
<?php
class SeasonStatService {
private $db;
private $cache;
public function __construct($pdo, $redis = null) {
$this->db = $pdo;
$this->cache = $redis;
}
public function getPlayerComparison($playerId, $season1, $season2) {
// 优先走缓存
$cacheKey = "cmp_{$playerId}_{$season1}_{$season2}";
if ($this->cache && $data = $this->cache->get($cacheKey)) {
return json_decode($data, true);
}
// 计算逻辑(SQL + 对比)
// ...
$this->cache->setex($cacheKey, 3600, json_encode($result));
return $result;
}
}
总结方案选择
| 场景 | 推荐方案 |
|---|---|
| 小项目 / 数据量 < 10万行 | 直接 GROUP BY 查询,不需要缓存,代码最简。 |
| 数据量大 / 实时性要求低 | 使用 GROUP BY + Redis 缓存,减缓 DB 压力。 |
| 需要折线图 / 趋势对比 | 使用窗口函数 SUM() OVER(PARTITION BY...)。 |
| 全站排行榜 | 纯 SQL 完成 GROUP BY + ORDER BY + LIMIT,禁止 PHP 排序。 |
核心原则是:能用 SQL 聚合的绝不用 PHP 循环,能用缓存的绝不重复查询。