SQL实战:按月统计订单与顾客数的优化方案
2026/8/6 21:44:42 网站建设 项目流程

1. 项目背景与需求分析

最近在力扣(LeetCode)上刷到一道SQL实战题——1565号题"按月统计订单数与顾客数"。这道题看似简单,却涵盖了数据分析师日常工作中最基础的统计需求。作为电商数据分析的经典场景,按月统计的核心价值在于帮助业务方掌握经营趋势,识别销售淡旺季,优化库存和营销策略。

题目要求从订单表中提取两个关键指标:

  1. 每月订单总数(反映业务规模)
  2. 每月下单顾客数(反映用户活跃度)

这两个指标的组合分析能揭示重要业务洞察。例如:

  • 订单数增长但顾客数持平:说明老客复购率提升
  • 顾客数增长但订单数持平:可能新客转化效果不佳
  • 两者同步增长:业务健康扩张

2. 数据模型解析

假设我们有一个标准的订单表orders,其典型结构如下:

CREATE TABLE orders ( order_id INT PRIMARY KEY, customer_id INT NOT NULL, order_date DATE NOT NULL, amount DECIMAL(10,2) );

关键字段说明:

  • order_date:需要从中提取年月信息
  • customer_id:用于统计独立顾客数
  • 注意:实际业务中可能还需要考虑订单状态(如已取消订单不应计入)

3. SQL解决方案详解

3.1 基础实现方案

最直接的实现方式是使用DATE_FORMATEXTRACT函数提取年月,然后进行分组统计:

SELECT DATE_FORMAT(order_date, '%Y-%m') AS month, COUNT(order_id) AS order_count, COUNT(DISTINCT customer_id) AS customer_count FROM orders GROUP BY DATE_FORMAT(order_date, '%Y-%m') ORDER BY month;

3.2 进阶优化方案

对于大型数据集,我们可以优化查询性能:

SELECT EXTRACT(YEAR_MONTH FROM order_date) AS year_month, COUNT(*) AS order_count, COUNT(DISTINCT customer_id) AS customer_count FROM orders WHERE order_status = 'completed' -- 只统计有效订单 GROUP BY year_month ORDER BY year_month;

优化点说明:

  1. 使用EXTRACT函数比DATE_FORMAT性能更好
  2. 添加WHERE条件过滤无效订单
  3. YEAR_MONTH直接返回整数格式(如202307),便于排序

3.3 处理边界情况

实际业务中还需考虑:

  • 跨年数据:确保年份和月份组合正确
  • 空值处理:使用COALESCE保证结果完整
  • 日期范围:可以添加BETWEEN条件限制统计周期

改进后的健壮版本:

SELECT CONCAT( EXTRACT(YEAR FROM order_date), '-', LPAD(EXTRACT(MONTH FROM order_date), 2, '0') ) AS month, COUNT(order_id) AS order_count, COUNT(DISTINCT customer_id) AS customer_count FROM orders WHERE order_date BETWEEN '2022-01-01' AND '2023-12-31' AND order_status = 'completed' GROUP BY EXTRACT(YEAR FROM order_date), EXTRACT(MONTH FROM order_date) ORDER BY EXTRACT(YEAR FROM order_date), EXTRACT(MONTH FROM order_date);

4. 性能优化技巧

4.1 索引策略

为提高查询效率,建议创建复合索引:

CREATE INDEX idx_orders_date_status ON orders(order_date, order_status);

对于顾客数统计,可以额外添加:

CREATE INDEX idx_orders_customer ON orders(customer_id);

4.2 分区表方案

当数据量极大时(如亿级记录),考虑按月分区:

CREATE TABLE orders ( ... ) PARTITION BY RANGE (TO_DAYS(order_date)) ( PARTITION p202201 VALUES LESS THAN (TO_DAYS('2022-02-01')), PARTITION p202202 VALUES LESS THAN (TO_DAYS('2022-03-01')), ... );

4.3 物化视图

对于频繁执行的统计查询,可以使用物化视图定期刷新:

CREATE MATERIALIZED VIEW monthly_stats AS SELECT ... (同上查询语句) REFRESH COMPLETE EVERY 1 DAY;

5. 业务应用扩展

5.1 同比环比分析

在基础统计上增加业务分析维度:

SELECT month, order_count, customer_count, LAG(order_count, 12) OVER (ORDER BY month) AS prev_year_order, ROUND((order_count - LAG(order_count, 12) OVER (ORDER BY month)) / LAG(order_count, 12) OVER (ORDER BY month) * 100, 2) AS yoy_rate FROM ( -- 基础统计查询 ) t ORDER BY month;

5.2 顾客分层统计

分析新老客贡献:

WITH first_orders AS ( SELECT customer_id, MIN(order_date) AS first_order_date FROM orders GROUP BY customer_id ) SELECT DATE_FORMAT(o.order_date, '%Y-%m') AS month, COUNT(DISTINCT CASE WHEN DATE_FORMAT(o.order_date, '%Y-%m') = DATE_FORMAT(f.first_order_date, '%Y-%m') THEN o.customer_id END) AS new_customers, COUNT(DISTINCT CASE WHEN DATE_FORMAT(o.order_date, '%Y-%m') > DATE_FORMAT(f.first_order_date, '%Y-%m') THEN o.customer_id END) AS repeat_customers FROM orders o JOIN first_orders f ON o.customer_id = f.customer_id GROUP BY DATE_FORMAT(o.order_date, '%Y-%m') ORDER BY month;

6. 常见问题与解决方案

6.1 时区问题

当数据来自不同时区时:

SELECT DATE_FORMAT(CONVERT_TZ(order_date, '+00:00', '+08:00'), '%Y-%m') AS month, ...

6.2 性能瓶颈

当统计速度变慢时检查:

  1. 是否使用了DISTINCT导致临时表过大
  2. 是否缺少合适的索引
  3. 是否可以考虑采样统计代替全量统计

6.3 数据倾斜

某些月份数据量异常大的处理方案:

  1. 增加WHERE条件分段查询
  2. 使用/*+ MAX_EXECUTION_TIME(30000) */限制执行时间
  3. 考虑使用近似统计函数如APPROX_COUNT_DISTINCT

7. 可视化建议

统计结果通常需要可视化呈现,推荐两种形式:

  1. 双轴折线图:左轴订单数,右轴顾客数,观察两者关系
  2. 热力图:横轴月份,纵轴年份,用颜色深浅表示增长幅度

SQL结果可以直接导出到可视化工具:

-- MySQL导出CSV SELECT ... INTO OUTFILE '/tmp/monthly_stats.csv' FIELDS TERMINATED BY ',' ENCLOSED BY '"' LINES TERMINATED BY '\n';

8. 不同数据库方言实现

8.1 PostgreSQL版本

SELECT TO_CHAR(order_date, 'YYYY-MM') AS month, COUNT(order_id) AS order_count, COUNT(DISTINCT customer_id) AS customer_count FROM orders GROUP BY TO_CHAR(order_date, 'YYYY-MM') ORDER BY month;

8.2 SQL Server版本

SELECT FORMAT(order_date, 'yyyy-MM') AS month, COUNT(order_id) AS order_count, COUNT(DISTINCT customer_id) AS customer_count FROM orders GROUP BY FORMAT(order_date, 'yyyy-MM') ORDER BY month;

8.3 Oracle版本

SELECT TO_CHAR(order_date, 'YYYY-MM') AS month, COUNT(order_id) AS order_count, COUNT(DISTINCT customer_id) AS customer_count FROM orders GROUP BY TO_CHAR(order_date, 'YYYY-MM') ORDER BY month;

9. 实际业务思考

在真实电商场景中,按月统计只是最基础的维度。我们还需要考虑:

  1. 节假日影响:春节、双11等特殊月份需要单独分析
  2. 用户生命周期:统计不同注册时间的用户群体表现
  3. 商品维度:结合品类分析订单构成
  4. 地域维度:不同地区的销售趋势

一个完整的业务分析SQL可能包含多个CTE:

WITH monthly_stats AS (...), category_stats AS (...), region_stats AS (...) SELECT ... FROM monthly_stats JOIN category_stats ON ... JOIN region_stats ON ...

10. 学习建议

要掌握这类时间序列统计,建议:

  1. 熟练使用各种日期函数
  2. 理解GROUP BY的执行原理
  3. 掌握窗口函数用于同比环比分析
  4. 学习使用EXPLAIN分析查询计划
  5. 在力扣上练习相关题目:
      1. 餐馆营业额变化增长
      1. 净现值查询
      1. 最近的三笔订单

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询