如何在非规范化数据库中按 ID 和月份统计条目数量

11次阅读

如何在非规范化数据库中按 ID 和月份统计条目数量

本文介绍如何处理 `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 等衍生需求,真正兼顾正确性、可维护性与扩展性。

text=ZqhQzanResources