如何在 PHP 中精准查询 MySQL 数据库中指定年份与周数的费用记录

2次阅读

如何在 PHP 中精准查询 MySQL 数据库中指定年份与周数的费用记录

本文讲解如何解决因多条同周号(如 w10)但跨年数据导致的查询歧义问题,通过联合 week 与 year 字段进行精确匹配,并提供安全、可维护的 php + pdo 实现方案。

本文讲解如何解决因多条同周号(如 w10)但跨年数据导致的查询歧义问题,通过联合 `week` 与 `year` 字段进行精确匹配,并提供安全、可维护的 php + pdo 实现方案。

在实际业务系统中,仅依赖 week 字段(如 ‘w10’)进行数据库查询极易引发数据混淆——尤其当表中存在多个年份的相同周编号记录(例如 w10 同时出现在 2021 和 2022 年)。您当前的代码未限定年份,导致 $base_charge = $stmt->fetch(…) 随机返回第一条匹配记录(如 ID=1 的 2021 年数据),而后续关联查询 expense WHERE week = ? 却误传了 $base_charge[‘id’](本应是主键,但此处逻辑错用为周标识),进一步加剧数据错位。

根本解决方案:始终以 (week, year) 作为联合查询条件,杜绝歧义。 推荐以下两种生产就绪型实现方式:

✅ 方案一:服务端自动绑定当前年份(推荐用于“默认显示本周”场景)

// 获取当前年份(4位)和ISO周数(两位,自动补零) $currentYear = (int)date('Y'); $currentWeek = (int)date('W'); // 注意:date('W') 返回纯数字(如 10),非 'w10' $weekParam = 'w' . str_pad($currentWeek, 2, '0', STR_PAD_LEFT); // 转为 'w10'  if (isset($_GET['selectWeek'])) {     // 从 URL 解析 week 字符串,如 'w46' → 提取数字 46     if (preg_match('/^w(d{1,2})$/i', $_GET['selectWeek'], $matches)) {         $weekParam = $_GET['selectWeek'];         $targetWeek = (int)$matches[1];     } else {         die('Invalid week format. Use e.g., ?selectWeek=w46');     } } else {     $targetWeek = $currentWeek; }  // ✅ 精确查询:同时匹配 week 字符串和 year 数值 $stmt = $pdo->prepare(     'SELECT * FROM expense_base_charge       WHERE week = ? AND year = ?       ORDER BY created_at DESC       LIMIT 1' ); $stmt->execute([$weekParam, $currentYear]); $base_charge = $stmt->fetch(PDO::FETCH_ASSOC);  if (!$base_charge) {     $msg = "No expense base charge found for week {$weekParam} in year {$currentYear}."; }

⚠️ 关键注意

  • date(‘W’) 返回的是纯数字(如 10),而您的数据库 week 字段存储为 ‘w10’,需手动拼接;若可修改表结构,强烈建议将 week 改为 TINYINT 类型并存数字,提升索引效率与可读性。
  • 添加 ORDER BY created_at DESC LIMIT 1 确保即使存在重复数据也稳定返回最新记录。
  • 使用 preg_match 安全解析 URL 参数,防止 sql 注入或格式错误。

✅ 方案二:显式支持年份+周数双参数(推荐用于灵活筛选场景)

升级 URL 接口为 ?selectWeek=46&selectYear=2023,后端直接使用整型比较:

立即学习PHP免费学习笔记(深入)”;

// 安全获取并校验参数 $targetWeek = filter_input(INPUT_GET, 'selectWeek', FILTER_VALIDATE_INT, [     'options' => ['min_range' => 1, 'max_range' => 53] ]) ?? (int)date('W');  $targetYear = filter_input(INPUT_GET, 'selectYear', FILTER_VALIDATE_INT, [     'options' => ['min_range' => 2020, 'max_range' => 2030] ]) ?? (int)date('Y');  // ✅ 直接数值匹配(假设 week 字段已改为 INT 类型) $stmt = $pdo->prepare(     'SELECT * FROM expense_base_charge       WHERE week = ? AND year = ?       ORDER BY created_at DESC       LIMIT 1' ); $stmt->execute([$targetWeek, $targetYear]); $base_charge = $stmt->fetch(PDO::FETCH_ASSOC);

? 进阶建议

  • 在 expense_base_charge(week, year) 上创建复合索引:CREATE INDEX idx_week_year ON expense_base_charge(week, year);
  • 若必须保留 ‘wXX’ 字符串格式,可添加生成列(mysql 5.7+):
    ALTER TABLE expense_base_charge  ADD COLUMN week_num TINYINT AS (CAST(SUBSTR(week, 2) AS UNSIGNED)) STORED; CREATE INDEX idx_weeknum_year ON expense_base_charge(week_num, year);

最后,请修正关联查询逻辑:您原代码中 $stmt->execute([$base_charge[‘id’]]) 尝试用主键 id 去匹配 expense.week 字段,这明显语义错误。正确做法应是:

// ✅ 关联 expense 表时,应使用相同的 week & year 条件 if ($base_charge) {     $stmt = $pdo->prepare(         'SELECT * FROM expense           WHERE week = ? AND year = ?           ORDER BY created_at DESC'     );     $stmt->execute([$base_charge['week'], $base_charge['year']]);     $expense = $stmt->fetchAll(PDO::FETCH_ASSOC); }

通过以上改造,系统将严格按“年+周”二维坐标定位数据,彻底消除跨年同周号导致的混乱,同时提升安全性、可维护性与查询性能。

text=ZqhQzanResources