一:简介
1:项目概述
电商数据仓库分析平台
2:核心人物
通过SQL开发,将原始订单数据转化为可供业务部门直接使用的分析报表。
3:项目价值
这类项目是数据开发岗位的核心工作,能全面提升你处理真实业务需求、优化复杂查询的能力。
二:项目需求与开发路线图
整个项目分为四个阶段,每个阶段对应一组具体的开发需求和需要掌握的SQL技能:
三:开发阶段
3.1: 开发阶段分析
| 阶段 | 开发需求 | 核心SQL技能与产出 |
|---|---|---|
| 阶段一:数据准备与探查 | 1. 创建原始订单表、用户表、商品表 2. 生成并探查模拟数据,了解数据全貌和质量 | CREATE TABLE, INSERT, SELECT基础查询, COUNT, DISTINCT, WHERE过滤 |
| 阶段二:核心数据模型开发 | 1. 创建"用户订单宽表",聚合用户级别的关键指标 2. 创建"商品销售聚合表",分析商品表现 | JOIN多表连接, GROUP BY聚合, SUM, AVG, CASE WHEN条件逻辑, CREATE VIEW |
| 阶段三:复杂业务指标开发 | 1. 计算用户复购率与留存率 2. 分析商品销售关联性(哪些商品常被一起购买) | 窗口函数(ROW_NUMBER, LAG), 自连接, 公共表表达式(WITH...AS) |
| 阶段四:性能优化与维护 | 1. 为关键查询表建立索引 2. 将复杂查询物化为表,提升报表查询速度 | CREATE INDEX, EXPLAIN分析执行计划, CREATE TABLE ... AS SELECT |
3.2:开发阶段的设计
第一阶段是数据基础,让用户熟悉环境和数据;
第二阶段聚焦核心业务指标,这是产品经理最常问的问题;
第三阶段进入高级分析,涉及用户行为分析和商品关联挖掘,这是数据开发工程师的价值体现;
第四阶段则是性能和生产化考量,体现工程思维。
四:需求
以下四个阶段模拟了从数据基础到高阶分析、再到性能优化的完整工作流。
| 阶段 | 核心目标 | 具体开发需求与分析任务 | 关键技能与产出 |
|---|---|---|---|
| 阶段一:数据探查与质量核查 | 理解数据,发现潜在问题 | 任务1.1:数据概览 • 查询各表总记录数、时间范围 • 检查是否存在NULL值 任务1.2:业务逻辑校验 • 验证fact_order_details.subtotal是否等于quantity * price • 检查是否有用户购买了自己所在城市不配送的商品(模拟) |
COUNT, MIN/MAX, WHERE IS NULL, JOIN基础 |
| 阶段二:核心业务指标开发 | 产出可直接用于报表的聚合数据 | 任务2.1:创建用户宽表 • 聚合每个用户的累计订单数、总金额、最近购买日 • 标记近30天活跃用户 任务2.2:商品销售分析表 • 计算每个商品的销售总量、总销售额、平均售价 • 按商品类目聚合销售额和销量排名 | GROUP BY聚合, CASE WHEN, CREATE VIEW, RANK() |
| 阶段三:高级业务分析 | 挖掘数据深层价值,支持决策 | 任务3.1:用户复购分析 • 计算每月用户的复购率(当月购买≥2次) • 找出累计消费金额最高的"高价值用户" 任务3.2:商品关联分析 • 找出最常被一起购买的商品组合(如:智能手机+无线耳机) • 分析"电子产品"类用户的交叉购买率 | 窗口函数, 自连接, 公共表表达式(CTE) |
| 阶段四:性能优化与脚本化 | 提升查询效率,实现自动化 | 任务4.1:查询性能优化 • 为fact_orders(order_date, user_id)创建复合索引 • 使用EXPLAIN分析慢查询的执行计划 任务4.2:数据快照与维护 • 创建"每日销售汇总"物化表,记录每天的核心指标 • 编写存储过程,一键清理测试订单数据 |
CREATE INDEX, EXPLAIN, CREATE TABLE AS, 存储过程基础 |
五:数据开发过程
sql
---1:创建数据库
create database ecommerce_analysis;
use ecommerce_analysis
---2:用户维度表
create table dim_users(
user_id int primary key,
user_name varchar(50),
city varchar(50),
registration_date date
)
---3:商品维护表
create table dim_products(
product_id int primary key,
product_name varchar(100),
category varchar(50) comment'电子产品',
price decimal(10,2)
)
---4:订单事实表(核心交易记录)
create table fact_orders(
order_id varchar(20) primary key,
user_id int,
order_date date,
total_amount decimal(10,2),
foreign key(user_id) references dim_users(user_id)
)
---5:订单明细表(记录订单中具体商品)
create table fact_order_details(
detail_id int auto_increment primary key,
order_id varchar(20),
product_id int,
quantity int ,
subtotal decimal(10,2),
foreign key(order_id) references fact_orders(order_id),
foreign key(product_id) references dim_products(product_id)
)
---6: 插入数据
insert into dim_users (user_id,user_name,city,registration_date) values
(101,'张三','北京','2023-01-15'),
(102,'李四','上海','2023-02-20'),
(103,'王五','北京','2023-03-10'),
(104,'赵六','广州','2023-01-05'),
(105,'钱七','上海','2023-04-18')
insert into dim_products(product_id,product_name,category,price) values
(1,'智能手机','电子产品',2999.00),
(2,'无线耳机','电子产品',399.00),
(3,'男士T恤','服装',89.00),
(4,'女士外套','服装',259.00),
(5,'陶瓷碗套装','家居',120.00)
insert into fact_orders(order_id,user_id,order_date,total_amount) values
('ORD202305001', 101, '2023-05-10', 3398.00),
('ORD202305002', 102, '2023-05-12', 399.00),
('ORD202305003', 103, '2023-05-15', 548.00),
('ORD202305004', 101, '2023-05-20', 259.00),
('ORD202305005', 104, '2023-05-22', 3278.00),
('ORD202306001', 102, '2023-06-05', 176.00),
('ORD202306002', 105, '2023-06-08', 120.00);
INSERT INTO fact_order_details (order_id, product_id, quantity, subtotal) VALUES
('ORD202305001', 1, 1, 2999.00),
('ORD202305001', 2, 1, 399.00),
('ORD202305002', 2, 1, 399.00),
('ORD202305003', 3, 2, 178.00),
('ORD202305003', 4, 1, 259.00),
('ORD202305003', 5, 1, 120.00),
('ORD202305004', 4, 1, 259.00),
('ORD202305005', 1, 1, 2999.00),
('ORD202305005', 5, 1, 120.00),
('ORD202306001', 3, 2, 178.00),
('ORD202306002', 5, 1, 120.00);
1: 阶段一 - 数据探查与质量核查
目的:理解数据,发现潜在问题
任务1:数据概览
1:查询各表总记录数,时间范围;
2:检查是否存在NULL值
任务2:业务逻辑校验
1:验证fact_order_details.subtotal 是否等于quantity * price
2: 检查是否有用户购买了自己所在城市不配送的商品(模拟)
关键技能与产出
count,max/min ,where is null,, join基础;
标:像一个数据侦探,在开始分析前,先摸清数据的"家底"和质量。
| 任务 | SQL语句与关键操作 | 执行结果(基于样本数据) | 结果分析与洞察 |
|---|---|---|---|
| 1.1 数据概览 | SELECT COUNT(*) AS cnt FROM fact_orders; SELECT MIN(order_date), MAX(order_date) FROM fact_orders; |
cnt: 7 日期范围:2023-05-10 到 2023-06-08 | 共7笔订单,时间跨度为约1个月。这是一个很小的数据集,适用于练习,但真实场景中应有更大数据量。 |
| 1.2 逻辑校验 | SELECT d.* FROM fact_order_details d JOIN dim_products p ON d.product_id = p.product_id WHERE ABS(d.subtotal - (d.quantity * p.price)) > 0.01; |
查询结果为空 | 没有发现明细金额与单价*数量不符的记录。通过基础数据一致性校验。这说明数据录入或ETL过程是准确的。 |
阶段一核心收获:你学会了在分析前首先要做"体检",确认数据量、时间范围和核心逻辑是否正确。这是所有可靠分析的地基。
sql
---阶段一
---数据概览
---1:查询订单表总记录数,时间范围
select count(*) from dim_users
select min(order_date),max(order_date) from fact_orders
---2:查询是否存在null值
select * from fact_orders where isnull(order_id)
---业务逻辑校验
---3:验证fact_order_details.subtotal 是否等于quantity * price(验证订单明细的产品总价格 = 产品价格 x 产品购买数量)
---目的: 没有发现明细金额与单价*数量不符的记录。通过基础数据一致性校验。这说明数据录入或ETL过程是准确的
---结果为空,没误差,数据准确
select * from dim_products a join fact_order_details b on a.product_id = b.product_id
where abs(b.subtotal - (a.price * b.quantity)) > 0.01
2:阶段二
目标:将原始数据加工成可直接使用的业务指标,通常是创建"数据宽表"或"汇总视图"。
sql
use ecommerce_analysis
---任务:创建用户宽表
---1:聚合每个用户的累计订单数,总金额(fact_order_details),商品总件数,最近购买日
select a.user_id,count(distinct b.order_id),count(c.detail_id),sum(c.subtotal) from dim_users a left join fact_orders b
on a.user_id = b.user_id
left join fact_order_details c
on b.order_id = c.order_id
group by a.user_id
---2:聚合每个用户的累计订单数,总金额(fact_order),商品总件数,最近购买日
--- 这里不能用sum(distinct b.total_amount) ,dinstinct去重,因为有不同的订单金额相同情况下,这样会少算;
select a.user_id,count(distinct b.order_id),count(c.detail_id),sum(distinct b.total_amount) from dim_users a left join fact_orders b
on a.user_id = b.user_id
left join fact_order_details c
on b.order_id = c.order_id
group by a.user_id
---正确方法
select
a.user_id ,
-- 从订单表直接获取订单数和金额
coalesce(b.total_orders,0) as total_orders,
-- 从订单明细获取商品件
coalesce (b.total_amount,0) as total_amount,
coalesce (c.total_items,0) as total_items
from dim_users a
left join(
--- 每个用户的订单聚合
select
user_id , --- 关键,这里要order_id
order_id,
count(*) as total_orders ,
sum(total_amount) as total_amount,
max(order_date)
from fact_orders fo
group by user_id
) b on a.user_id = b.user_id
left join(
-- 每个用户的商品件数聚和
select order_id , count(*) as total_items from fact_order_details c group by order_id
)c on c.order_id = b.order_id
注意:c.order_id = b.order_id 报错;不存在b.order_id ,因为b子连接查询中要添加order_id
2:计算每个商品的销售总量,总销售额,平均售价
sql
select
dp.product_id,
dp.product_name,
sum(fod.quantity) as total_quantity,
sum(fod.subtotal) as total_price,
avg(fod.subtotal / fod.quantity) as avg_price
from dim_products dp
left join fact_order_details fod
ON dp.product_id = fod .product_id
group by dp.product_id
order by product_id asc
以上SQL存在风险:
avg_price的计算隐患 :你使用fod.subtotal / fod.quantity作为AVG的参数。这里存在两个风险:
除零错误 :如果某条明细的
quantity为 0,数据库会抛出除零错误。NULL值干扰 :
AVG函数会忽略NULL。如果subtotal或quantity为NULL,会导致除法结果为NULL,从而被AVG忽略,这可能扭曲最终的平均值。无销售商品的结果 :由于使用
LEFT JOIN,没有销售记录的商品,其SUM结果将是NULL而不是 0。在报表中,NULL通常不如 0 直观。
优化SQL
sql
SELECT
dp.product_id,
dp.product_name,
-- 使用COALESCE将NULL转换为0,使报表更清晰
COALESCE(SUM(fod.quantity), 0) AS total_quantity_sold,
COALESCE(SUM(fod.subtotal), 0) AS total_sales_amount,
-- 安全地计算平均售价:总销售额 / 总销售数量
-- 使用NULLIF避免除零错误,当总数量为0时,平均价显示为NULL(可再用COALESCE赋默认值)
COALESCE(
SUM(fod.subtotal) / NULLIF(SUM(fod.quantity), 0),
0
) AS avg_selling_price
FROM
dim_products dp
LEFT JOIN
fact_order_details fod ON dp.product_id = fod.product_id
GROUP BY
dp.product_id,
dp.product_name -- 通常需要将SELECT中所有非聚合字段都加入GROUP BY
ORDER BY
dp.product_id ASC; -- 显式指明表别名是更好的实践
3:按商品类目聚合销售额和类目的销售排名
sql
select
dp.category,
coalesce (sum(fod.subtotal),0) as cate_total_price,
row_number() OVER(order by coalesce(sum(fod.subtotal),0) desc) as cate_rank
from dim_products dp
left join fact_order_details fod
on dp.product_id = fod.product_id
group by dp.category
order by cate_total_price desc