本文目录导读:

在 PHP 项目中实现周度统计,核心在于处理好“周”的定义(如周一至周日还是周日到周六?是否包含跨年周?)以及高效的 SQL 查询。
以下是实现步骤和最佳实践。
明确定义 “统计周期”
首先要统一业务逻辑中“周”的定义,常见的定义有:
| 类型 | 定义 | 对应 SQL函数/常数 |
|---|---|---|
| ISO 8601 标准 | 周一为第一天,周四为分割,一年52周 | YEARWEEK(date, 1) 或 WEEK(date, 1) |
| 默认(周日为首) | 周日为第一天 | YEARWEEK(date) 或 WEEK(date) |
| 自定义(周一为首) | 周一为第一天,周日结束 | WEEK(date, 1) |
| 自定义(周日为末) | 周日为最后一天 | WEEK(date, 0) (取决于MySQL版本) |
建议: 在项目设计文档中明确记录你选用的规则,通常推荐 ISO 8601(周一为首)。
数据库设计建议
为了快速进行周统计,建议在数据库表中增加一个冗余字段:
ALTER TABLE `orders` ADD COLUMN `week_start` DATE NOT NULL COMMENT '所属周的周一日期'; ALTER TABLE `orders` ADD COLUMN `week_num` TINYINT UNSIGNED NOT NULL COMMENT '当年第几周'; ALTER TABLE `orders` ADD COLUMN `year_num` SMALLINT UNSIGNED NOT NULL COMMENT '年份';
插入数据时使用程序或触发器自动填充:
-- MySQL 示例:插入订单时自动计算所属周信息
INSERT INTO `orders` (`created_at`, `other_fields`, `week_start`, `week_num`, `year_num`)
VALUES (
NOW(),
'...',
DATE_SUB(NOW(), INTERVAL WEEKDAY(NOW()) DAY), -- 计算本周周一
WEEK(NOW(), 1), -- 当年第几周
YEAR(NOW())
);
优点: 统计时直接 GROUP BY week_start,无需每次计算,性能极高。
PHP 代码实现
方案 A:不使用冗余字段,直接用 SQL 计算(适合数据量小或动态查询)
<?php
/**
* 获取指定时间范围内的周度统计
* @param string $startDate 起始日期 '2024-01-01'
* @param string $endDate 结束日期 '2024-12-31'
* @return array
*/
function getWeeklyStats($startDate, $endDate) {
// 假设表结构:orders (id, created_at, amount)
// 使用 SQL 按 ISO 周分组
$sql = "
SELECT
DATE_SUB(created_at, INTERVAL WEEKDAY(created_at) DAY) AS week_start,
YEAR(created_at) AS year,
WEEK(created_at, 1) AS week_num,
COUNT(*) AS order_count,
SUM(amount) AS total_amount
FROM orders
WHERE created_at BETWEEN :start_date AND :end_date
GROUP BY week_start
ORDER BY week_start ASC
";
$stmt = $pdo->prepare($sql);
$stmt->execute([
':start_date' => $startDate,
':end_date' => $endDate
]);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
输出示例:
[
['week_start' => '2024-01-01', 'year' => 2024, 'week_num' => 1, 'order_count' => 123, 'total_amount' => '15000.00'],
['week_start' => '2024-01-08', 'year' => 2024, 'week_num' => 2, 'order_count' => 98, 'total_amount' => '12000.00'],
]
方案 B:使用冗余字段(推荐)
<?php
/**
* 利用冗余字段快速获取周度统计
*/
function getWeeklyStatsFast($startDate, $endDate) {
$sql = "
SELECT
week_start,
year_num AS year,
week_num,
COUNT(*) AS order_count,
SUM(amount) AS total_amount
FROM orders
WHERE week_start BETWEEN :start_date AND :end_date
GROUP BY week_start
ORDER BY week_start ASC
";
$stmt = $pdo->prepare($sql);
$stmt->execute([
':start_date' => $startDate,
':end_date' => $endDate
]);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
处理边界情况
1 跨年周的归类
如果订单发生在 2023-12-30(属于2024年第1周),需要明确它应统计到哪一年。
- 方案1(按 ISO 标准): 属于 2024年第1周
- 方案2(按实际年份): 属于 2023年的最后一周
SQL 实现:
// 方案1:ISO 年 + ISO 周 YEARWEEK(created_at, 1) // 方案2:实际年份 + 周(易出现歧义) YEAR(created_at) 和 WEEK(created_at, 0/1)
2 数据缺失的周(无数据)
统计结果可能某周没有数据,数组会缺少该周,为了展示连续性,可以预先生成日期补全。
<?php
/**
* 补全缺失的周
*/
function fillMissingWeeks($weeklyData, $startDate, $endDate) {
$result = [];
$current = new DateTime($startDate);
$end = new DateTime($endDate);
// 调整到周一
$dayOfWeek = (int)$current->format('N');
$current->modify('-' . ($dayOfWeek - 1) . ' days');
while ($current <= $end) {
$weekStart = $current->format('Y-m-d');
// 查找是否已有数据,若没有则填充默认值
$found = current(array_filter($weeklyData, fn($d) => $d['week_start'] === $weekStart));
$result[] = $found ?: [
'week_start' => $weekStart,
'year' => $current->format('Y'),
'week_num' => $current->format('W'),
'order_count' => 0,
'total_amount' => '0.00'
];
$current->modify('+7 days');
}
return $result;
}
性能优化建议
| 场景 | 建议 |
|---|---|
| 数据量 < 10万 | 直接用 SQL 函数 WEEK() 计算,简单易懂 |
| 数据量 10万~100万 | 添加 week_start 冗余字段,创建单列索引 |
| 数据量 > 100万 | 使用 物化视图(或定时任务生成汇总表,每日更新) |
| 需要实时统计 | 结合 Redis 缓存,缓存周统计结果,定期刷新 |
索引推荐:
-- 冗余字段方式 ALTER TABLE orders ADD INDEX idx_week_start (week_start); -- 动态计算方式(创建计算列索引,MySQL 5.7+) ALTER TABLE orders ADD COLUMN week_start DATE GENERATED ALWAYS AS (DATE_SUB(created_at, INTERVAL WEEKDAY(created_at) DAY)) STORED; ALTER TABLE orders ADD INDEX idx_calc_week_start (week_start);
完整示例代码(控制器层)
<?php
// WeekStatsController.php
class WeekStatsController {
private $pdo;
public function index(Request $request) {
// 默认统计最近12周
$endDate = date('Y-m-d');
$startDate = date('Y-m-d', strtotime('-12 weeks'));
// 获取原始统计
$rawData = $this->getWeeklyStats($startDate, $endDate);
// 补全缺失周
$weeklyData = $this->fillMissingWeeks($rawData, $startDate, $endDate);
// 返回数据
return response()->json([
'start_date' => $startDate,
'end_date' => $endDate,
'data' => $weeklyData
]);
}
}
- 定义号“周”的起止规则(推荐 ISO 标准:周一至周日)
- 数据库设计时考虑添加
week_start冗余字段并建立索引 - SQL 查询使用
GROUP BY week_start结合聚合函数 - PHP 处理时注意补全缺失周和跨年周的归属问题
- 性能优化:数据量大时使用定时任务预先汇总到统计表
这样就能高效、准确地获得 PHP 项目的周度统计数据了。