如何用最少PHP代码结合WHERE条件实现多科目考试成绩批量统计?
优化学校成绩统计的PHP代码方案
你当前的问题核心是重复代码过多——每个考试、每个性别分组都要写几乎一样的查询逻辑。用数组+循环或者更高效的SQL聚合查询可以彻底解决这个问题,下面给你两种实用方案:
方案1:用数组定义维度,循环生成查询(易理解,适合小规模场景)
先把需要统计的所有维度(考试场次、性别分组)用数组定义,然后通过循环批量处理,避免重复写SQL。同时记得用预处理语句防止SQL注入(你原来的直接拼接变量存在安全风险)。
代码示例:
// 1. 定义需要统计的维度数组 // 假设你的考试字段是chem1_1到chem1_8(chem1的8次考试)、chem2_1到chem2_8(chem2的8次考试) $exam_fields = [ 'chem1_1' => '化学1-第1次', 'chem1_2' => '化学1-第2次', // ... 补全到chem1_8 'chem2_1' => '化学2-第1次', // ... 补全到chem2_8 ]; // 性别分组:键是数据库中的sex值,值是显示用的名称 $gender_groups = [ 'M' => '男生', 'F' => '女生', 'all' => '全体' ]; // 2. 循环处理每个维度,批量统计 $stats = []; foreach ($exam_fields as $field => $exam_name) { foreach ($gender_groups as $sex_val => $gender_name) { // 构建WHERE条件:全体用户不需要sex筛选 $where_sex = ($sex_val === 'all') ? '' : "AND sex = ?"; // 预处理SQL:统计及格人数(假设及格线60)、总人数、平均分 $sql = " SELECT COUNT(*) AS total_students, COUNT(CASE WHEN $field >= 60 THEN 1 END) AS pass_students, AVG($field) AS avg_score FROM test_2324 WHERE cls = ? AND section = ? $where_sex "; // 初始化预处理语句 $stmt = mysqli_prepare($db, $sql); // 绑定参数:根据是否是全体,绑定不同数量的参数 if ($sex_val === 'all') { mysqli_stmt_bind_param($stmt, "ss", $cls_show, $sect_show); } else { mysqli_stmt_bind_param($stmt, "sss", $cls_show, $sect_show, $sex_val); } // 执行查询 mysqli_stmt_execute($stmt); // 获取结果 $result = mysqli_stmt_get_result($stmt); $row = mysqli_fetch_assoc($result); // 计算及格率(避免除以0) $pass_rate = $row['total_students'] > 0 ? ($row['pass_students'] / $row['total_students']) * 100 : 0; // 存储统计结果 $stats[$exam_name][$gender_name] = [ 'total' => $row['total_students'], 'pass_count' => $row['pass_students'], 'pass_rate' => round($pass_rate, 2) . '%', 'avg_score' => round($row['avg_score'], 2) ]; // 关闭语句 mysqli_stmt_close($stmt); } } // 3. 输出统计结果(示例) foreach ($stats as $exam => $gender_data) { echo "<h3>$exam</h3>"; foreach ($gender_data as $gender => $data) { echo "<p>$gender:总人数{$data['total']},及格率{$data['pass_rate']},平均分{$data['avg_score']}</p>"; } }
方案2:用SQL聚合+GROUP BY一次性获取所有统计(更高效,减少数据库请求)
如果数据量较大,多次查询会影响性能,推荐用SQL的GROUP BY和WITH ROLLUP一次性统计所有性别分组(包括全体),同时计算多个考试场次的指标。
代码示例:
// 1. 定义需要统计的考试字段(和方案1一致) $exam_fields = [ 'chem1_1', 'chem1_2', /* ... 补全到chem1_8 */ 'chem2_1', /* ... 补全到chem2_8 */ ]; // 2. 动态构建SQL的统计字段 $select_clauses = []; foreach ($exam_fields as $field) { $select_clauses[] = "COUNT(CASE WHEN $field >= 60 THEN 1 END) AS {$field}_pass"; $select_clauses[] = "AVG($field) AS {$field}_avg"; } $select_part = implode(', ', $select_clauses); // 3. 构建完整SQL:用GROUP BY WITH ROLLUP自动生成全体统计 $sql = " SELECT sex, COUNT(*) AS total_students, $select_part FROM test_2324 WHERE cls = ? AND section = ? GROUP BY sex WITH ROLLUP "; // 4. 预处理执行 $stmt = mysqli_prepare($db, $sql); mysqli_stmt_bind_param($stmt, "ss", $cls_show, $sect_show); mysqli_stmt_execute($stmt); $result = mysqli_stmt_get_result($stmt); // 5. 解析结果 $stats = []; while ($row = mysqli_fetch_assoc($result)) { // 判断当前行是男生、女生还是全体(ROLLUP生成的总行sex为NULL) $gender = match($row['sex']) { 'M' => '男生', 'F' => '女生', null => '全体' }; $stats[$gender] = [ 'total' => $row['total_students'] ]; // 遍历每个考试字段,提取及格数、平均分并计算及格率 foreach ($exam_fields as $field) { $pass_count = $row["{$field}_pass"]; $avg_score = $row["{$field}_avg"]; $pass_rate = $row['total_students'] > 0 ? ($pass_count / $row['total_students']) * 100 : 0; $stats[$gender][$field] = [ 'pass_count' => $pass_count, 'pass_rate' => round($pass_rate, 2) . '%', 'avg_score' => round($avg_score, 2) ]; } } // 6. 输出结果(示例) foreach ($stats as $gender => $data) { echo "<h3>$gender</h3>"; echo "<p>总人数:{$data['total']}</p>"; foreach ($exam_fields as $field) { echo "<p>$field:及格人数{$data[$field]['pass_count']},及格率{$data[$field]['pass_rate']},平均分{$data[$field]['avg_score']}</p>"; } }
关键注意事项
- SQL注入防护:必须用预处理语句(
mysqli_prepare)绑定参数,绝对不能直接把$cls_show、$sect_show这类用户输入拼接进SQL。 - 空值处理:计算平均分、及格率时要判断总人数是否为0,避免出现除以0的错误。
- 字段命名:如果你的考试字段命名规则不同(比如不是
chemX_Y),只需要调整$exam_fields数组即可,不需要修改核心逻辑。
内容的提问来源于stack exchange,提问作者Goutham Tamil
相关产品推荐
相关产品推荐

