分类课程智能体AI
文章
订阅
分类课程AI导师
文章
价格
课程进度
6 / 12
上一节建立可复核的经营指标下一节从品类贡献到商品组合
自在学

© 2025 - 2026 株洲市自在学教育科技有限公司 版权所有

公网安备湘公网安备43020302000292号 | 湘ICP备2025148919号-1

关于我们隐私政策使用条款

© 2025 - 2026 株洲市自在学教育科技有限公司 版权所有

公网安备湘公网安备43020302000292号湘ICP备2025148919号-1

编程SQL + Excel 数据分析实战沿着时间轴观察经营变化

沿着时间轴观察经营变化

总销售额只能说明整个期间卖了多少,不能说明增长发生在什么时候。时间分析要先统一周期,再比较相邻周期,最后用移动平均减少单月波动。


汇总月度指标

销售视图已经准备了 order_month,可以直接按月分组:

sql
SELECT
    order_month,
    COUNT(DISTINCT order_id) AS orders,
    COUNT(DISTINCT customer_id) AS customers,
    ROUND(SUM(revenue), 2) AS revenue,
    ROUND(SUM(gross_profit), 2) AS gross_profit,
    ROUND(
        SUM(revenue) / COUNT(DISTINCT order_id),
        2
    ) AS avg_order_value
FROM v_sales_detail
GROUP BY order_month
ORDER BY order_month;

结果从 2024-01 连续到 2025-06,共 18 个月。先检查月份是否缺失。如果某月没有订单,这个查询不会自动生成该月,折线图会直接从前一个月连到后一个月。正式报表通常要准备日历表补齐空周期。

本项目恰好每个月都有完成订单,所以可以直接继续。如果检查结果出现断月,使用递归 CTE 生成连续月份,再左连接月度结果:

sql
WITH RECURSIVE
bounds AS (
    SELECT
        MIN(order_month) AS min_month,
        MAX(order_month) AS max_month
    FROM v_sales_detail
), calendar(month_start, max_month) AS (
    SELECT date(min_month || '-01'), date(max_month || '-01')
    FROM bounds
    UNION ALL
    SELECT date(month_start, '+1 month'), max_month
    FROM calendar
    WHERE month_start < max_month
), monthly AS (
    SELECT
        order_month,
        COUNT(DISTINCT order_id) AS orders,
        SUM(revenue) AS revenue
    FROM v_sales_detail
    GROUP BY order_month
)
SELECT
    strftime('%Y-%m', c.month_start) AS order_month,
    COALESCE(m.orders, 0) AS orders,
    ROUND(COALESCE(m.revenue, 0), 2) AS revenue
FROM calendar AS c
LEFT JOIN monthly AS m
  ON m.order_month = strftime('%Y-%m', c.month_start)
ORDER BY c.month_start;

这里将无订单月份补为 0,是因为“该月没有完成订单”本身就是一个可解释的经营事实。若月份缺失是因为数据尚未同步,则不能补 0,应先修复数据源,否则图表会把“未知”误画成“没有销售”。


用 LAG 计算环比

LAG 能取得排序后上一行的值。月度数据一行一月,所以“上一行”就是上一个月。

sql
WITH monthly AS (
    SELECT
        order_month,
        COUNT(DISTINCT order_id) AS orders,
        ROUND(SUM(revenue), 2) AS revenue
    FROM v_sales_detail
    GROUP BY order_month
), compared AS (
    SELECT
        *,
        LAG(revenue) OVER (
            ORDER BY order_month
        ) AS previous_revenue
    FROM monthly
)
SELECT
    order_month,
    orders,
    revenue,
    previous_revenue,
    ROUND(
        (revenue - previous_revenue) / previous_revenue,
        4
    ) AS revenue_mom
FROM compared
ORDER BY order_month;
order_monthordersrevenueprevious_revenuerevenue_mom
2024-012813278.95
2024-022812865.8713278.95-0.0311
2024-033013862.0612865.870.0774
2024-042812728.3413862.06-0.0818

第一个月没有上月数据,所以环比为空。不要把它填成 0;0 表示“与上月持平”,而这里是“无法比较”。

窗口函数不会减少行数。它先按 order_month 排序,再把上一行的销售额放到当前行。以 2024-02 为例:

环比=12865.87−13278.9513278.95=−3.11%\text{环比}=\frac{12865.87-13278.95}{13278.95}=-3.11\%环比=13278.9512865.87−13278.95​=−3.11%

为了防止上月销售额为 0,生产查询可以把分母写成 NULLIF(previous_revenue, 0)。结果为空比报出无意义的无穷大更诚实。同比则不是上一行,而是上一年同月,可以用 LAG(revenue, 12);只有数据至少覆盖 13 个月时,同比才开始有值。


用移动平均看方向

三个月移动平均使用当前月和前两个月:

sql
WITH monthly AS (
    SELECT
        order_month,
        ROUND(SUM(revenue), 2) AS revenue
    FROM v_sales_detail
    GROUP BY order_month
)
SELECT
    order_month,
    revenue,
    ROUND(
        AVG(revenue) OVER (
            ORDER BY order_month
            ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
        ),
        2
    ) AS revenue_ma3
FROM monthly
ORDER BY order_month;

窗口开头不足三个月时,SQLite 会用已有月份计算平均值。如果报表要求“满三个月才显示”,可以再用 ROW_NUMBER() 判断行号。

sql
WITH monthly AS (
    SELECT order_month, SUM(revenue) AS revenue
    FROM v_sales_detail
    GROUP BY order_month
), numbered AS (
    SELECT
        *,
        ROW_NUMBER() OVER (ORDER BY order_month) AS month_index,
        AVG(revenue) OVER (
            ORDER BY order_month
            ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
        ) AS ma3
    FROM monthly
)
SELECT
    order_month,
    ROUND(revenue, 2) AS revenue,
    CASE WHEN month_index >= 3 THEN ROUND(ma3, 2) END AS revenue_ma3
FROM numbered
ORDER BY order_month;

选择三个月不是统计定律,而是为了让短期波动变平,同时不把趋势拖得太迟。季节性强的业务还可以比较 12 个月移动平均;窗口越长,曲线越平滑,反应也越慢。


把销售额变化拆成订单数和客单价

销售额上升可能来自更多订单,也可能来自更高客单价。因为“销售额 = 订单数 × 客单价”,看到异常月份后要同时比较两个驱动因素:

sql
WITH monthly AS (
    SELECT
        order_month,
        COUNT(DISTINCT order_id) AS orders,
        SUM(revenue) / COUNT(DISTINCT order_id) AS aov
    FROM v_sales_detail
    GROUP BY order_month
), compared AS (
    SELECT
        *,
        LAG(orders) OVER (ORDER BY order_month) AS previous_orders,
        LAG(aov) OVER (ORDER BY order_month) AS previous_aov
    FROM monthly
)
SELECT
    order_month,
    orders,
    ROUND(aov, 2) AS aov,
    ROUND(1.0 * (orders - previous_orders) / NULLIF(previous_orders, 0), 4)
        AS orders_mom,
    ROUND((aov - previous_aov) / NULLIF(previous_aov, 0), 4)
        AS aov_mom
FROM compared
ORDER BY order_month;

如果销售额上升、订单数上升而客单价下降,可以解释为“更多订单拉动了增长,但每单金额变小”;下一步应检查新增客户、渠道或促销。不要仅凭一条销售额折线就写“经营改善”。


把趋势交给 Excel

后面会把月度结果导入 Excel。折线图使用“月份”为横轴、“销售额”为主线;订单数的量级较小,适合放在次坐标轴。

图表的用途是让变化更容易看见,不替代 SQL 结果。看到某个月上升或下降后,仍要回到订单、品类和客户维度查原因。

交给 Excel 的月度表保持“一行一月”,至少包含 order_month、orders、customers、revenue、gross_profit。客单价、毛利率、环比和移动平均会在 Excel 中再算一次:这不是重复劳动,而是让学习者看到数据库聚合结果如何变成可审阅的表格公式,并能与 SQL 口径互相对账。


判断波动是否值得追查

用绝对值筛出环比变化超过 8% 的月份:

sql
WITH monthly AS (
    SELECT order_month, SUM(revenue) AS revenue
    FROM v_sales_detail
    GROUP BY order_month
), compared AS (
    SELECT
        *,
        LAG(revenue) OVER (ORDER BY order_month) AS previous_revenue
    FROM monthly
)
SELECT
    order_month,
    ROUND(revenue, 2) AS revenue,
    ROUND((revenue - previous_revenue) / previous_revenue, 4) AS mom
FROM compared
WHERE ABS((revenue - previous_revenue) / previous_revenue) >= 0.08
ORDER BY order_month;

8% 不是通用标准,只是本项目的观察阈值。真实业务应结合历史波动、预算容忍度和促销日历定义。下一节会转向商品,判断销售额由哪些品类和商品组合构成。

排查异常月时按“先确认、再拆分、后解释”的顺序:先确认数据是否完整,再拆订单数与客单价,然后按城市、渠道和品类下钻。只有当多个维度的证据能够解释总额变化时,才把原因写进结论;促销、节假日等外部事件若没有数据佐证,只能写成待验证假设。

  • 汇总月度指标
  • 用 LAG 计算环比
  • 用移动平均看方向
  • 把销售额变化拆成订单数和客单价
  • 把趋势交给 Excel
  • 判断波动是否值得追查

目录

  • 汇总月度指标
  • 用 LAG 计算环比
  • 用移动平均看方向
  • 把销售额变化拆成订单数和客单价
  • 把趋势交给 Excel
  • 判断波动是否值得追查