SQL业务报表生成的核心是把分散在业务表中的数据处理成可供决策的汇总结果。通常我们需要先明确报表维度与指标,再通过编写SQL完成抽取、清洗和聚合,最后将结果落表或直出前端。下面以销售业务为例拆解实现过程。

一、明确报表需求与数据来源
在动手写SQL前,先确认报表要回答的问题。比如销售日报表需要展示每个门店每天的订单数、销售额和客单价。相关源表通常包含订单表 orders 和订单明细表 order_items。
1. 核心指标定义
- 订单数:按门店和日期去重统计 order_id
- 销售额:order_items 中 amount 求和
- 客单价:销售额除以订单数
二、编写基础SQL完成聚合
使用 GROUP BY 将订单与明细关联后按维度聚合。注意明细表需先按订单汇总,再关联订单表获取门店信息,避免重复计算。
-- 先汇总每个订单的金额 WITH order_amount AS ( SELECT order_id, SUM(amount) AS total_amount FROM order_items GROUP BY order_id ) -- 关联订单表并按门店和日期聚合 SELECT o.store_id, DATE(o.created_at) AS order_date, COUNT(DISTINCT o.order_id) AS order_cnt, SUM(oa.total_amount) AS sales_amount, SUM(oa.total_amount) / COUNT(DISTINCT o.order_id) AS avg_price FROM orders o JOIN order_amount oa ON o.order_id = oa.order_id WHERE o.status = 'paid' GROUP BY o.store_id, DATE(o.created_at) ORDER BY order_date, store_id;
2. 使用窗口函数做同比
若报表需要展示销售额环比,可以用 LAG 窗口函数获取前一日数据。
SELECT
store_id,
order_date,
sales_amount,
LAG(sales_amount, 1) OVER (PARTITION BY store_id ORDER BY order_date) AS prev_amount,
ROUND((sales_amount - LAG(sales_amount, 1) OVER (PARTITION BY store_id ORDER BY order_date))
/ LAG(sales_amount, 1) OVER (PARTITION BY store_id ORDER BY order_date), 4) AS growth_rate
FROM daily_sales;
三、结果落表与调度
将上面每日聚合的结果写入报表表 daily_sales,再通过定时任务调用。以下为 MySQL 事件示例,每天凌晨两点生成昨日报表。
CREATE EVENT IF NOT EXISTS gen_daily_sales
ON SCHEDULE EVERY 1 DAY STARTS '2020-01-01 02:00:00'
DO
INSERT INTO daily_sales (store_id, order_date, order_cnt, sales_amount, avg_price)
SELECT
o.store_id,
DATE(o.created_at) AS order_date,
COUNT(DISTINCT o.order_id) AS order_cnt,
SUM(oa.total_amount) AS sales_amount,
SUM(oa.total_amount) / COUNT(DISTINCT o.order_id) AS avg_price
FROM orders o
JOIN (
SELECT order_id, SUM(amount) AS total_amount
FROM order_items
GROUP BY order_id
) oa ON o.order_id = oa.order_id
WHERE o.status = 'paid'
AND DATE(o.created_at) = DATE_SUB(CURDATE(), INTERVAL 1 DAY)
GROUP BY o.store_id, DATE(o.created_at);
四、完整应用场景建议
在真实业务中,报表往往还需处理退单、优惠券抵扣等逻辑。建议在源表增加标记字段,在SQL中用 CASE WHEN 区分。例如将退款金额从销售额中扣除:
SELECT store_id, SUM(CASE WHEN o.refund_flag = 0 THEN oa.total_amount ELSE 0 END) AS net_sales FROM orders o JOIN order_amount oa ON o.order_id = oa.order_id GROUP BY store_id;
常见注意事项
- 大表聚合前确认有合适索引,如 orders(store_id, created_at)
- 避免在报表SQL中直接使用 SELECT *,只取所需字段
- 时间字段统一时区,防止跨天统计错位
通过以上步骤,你可以基于SQL搭建稳定的业务报表生成流程,并根据场景扩展维度和指标,支撑日常运营分析。