
本文介绍如何处理 `opid` 字段包含逗号分隔值的非规范化表,通过 php 预处理实现按单个操作 id(而非整条字符串)和月份精准计数,并生成结构化统计结果。
在实际开发中,我们偶尔会遇到设计不规范的数据库表——例如 tbl_operations 表中 OpId 字段并非原子值,而是以逗号分隔的字符串(如 '5,3' 或 '3,2')。这种非规范化结构使得纯 SQL 聚合统计变得异常困难:标准 GROUP BY 无法直接拆分字符串,而 MySQL 8.0+ 的 REGEXP_SUBSTR 或递归 CTE 虽可行,但兼容性差、可读性低,且在高并发或大数据量下性能堪忧。
因此,推荐采用“SQL + PHP 协同处理”的稳健方案:先用简单 SQL 获取原始数据,再由 PHP 完成字符串解析、月份提取与多维计数。该方法逻辑清晰、易于调试、完全兼容所有 PHP 环境(>=7.4),并便于后续扩展(如去重、过滤、导出等)。
以下是核心实现代码:
// 假设 $pdo 是已建立的 PDO 连接
$stmt = $pdo->query("SELECT OpId, OpDate FROM tbl_operations");
$rows = $stmt->fetchAll(PDO::FETCH_NUM);
$counts = [];
foreach ($rows as $row) {
[$opIdStr, $opDate] = $row;
$ids = array_map('trim', explode(',', $opIdStr)); // 自动去除空格(如 '5, 3')
$month = date('m', strtotime($opDate)); // 提取两位月份(如 '01')
foreach ($ids as $id) {
if (!isset($counts[$id][$month])) {
$counts[$id][$month] = 0;
}
$counts[$id][$month]++;
}
}
// 转换为题设要求的扁平化结果数组(便于渲染表格或 JSON 输出)
$result = [];
foreach ($counts as $opId => $months) {
foreach ($months as $month => $count) {
$result[] = ['OpId' => $opId, 'count' => $count, 'Month' => $month];
}
// 可选:按 OpId 升序、Month 升序排序,确保输出稳定
usort($result, function($a, $b) {
return $a['OpId'] <=> $b['OpId'] ?: $a['Month'] <=> $b['Month'];
});✅ 关键优势说明:
- array_map('trim', ...) 防止因空格导致 ' 3' 与 '3' 被视为不同 ID;
- isset() 检查比 array_key_exists() 更高效,且能同时判断嵌套键是否存在;
- usort() 确保结果顺序可控,避免因数组遍历不确定性影响测试或展示;
- 整体时间复杂度为 O(n×m),其中 n 为行数、m 为平均每行 ID 数量,性能线性可预期。
⚠️ 注意事项:
- 若数据量极大(如超 10 万行),建议在 PHP 层分批次处理,或改用数据库端 JSON 函数(MySQL 5.7+/PostgreSQL)预处理;
- 长期来看,强烈建议重构表结构:新建关联表 operation_ids(op_id, operation_id) 实现一对多关系,从根本上消除字符串解析需求;
- 生产环境务必对 strtotime($opDate) 做容错处理(如检查返回 false 并跳过非法日期)。
通过此方案,你不仅能准确复现题设中的统计表格,还能灵活支持分页、筛选、导出 CSV 等衍生需求,真正兼顾正确性、可维护性与扩展性。










