PHP项目怎样实现数据周度统计?

wen PHP项目 35

本文目录导读:

PHP项目怎样实现数据周度统计?

  1. 明确定义 “统计周期”
  2. 数据库设计建议
  3. PHP 代码实现
  4. 处理边界情况
  5. 性能优化建议
  6. 完整示例代码(控制器层)

在 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
        ]);
    }
}

  1. 定义号“周”的起止规则(推荐 ISO 标准:周一至周日)
  2. 数据库设计时考虑添加 week_start 冗余字段并建立索引
  3. SQL 查询使用 GROUP BY week_start 结合聚合函数
  4. PHP 处理时注意补全缺失周和跨年周的归属问题
  5. 性能优化:数据量大时使用定时任务预先汇总到统计表

这样就能高效、准确地获得 PHP 项目的周度统计数据了。

抱歉,评论功能暂时关闭!