一、项目介绍
1.1 项目背景
基于阿里母婴数据集和天池母婴交易历史数据,构建一个完整的母婴电商数据分析仓库,用于分析用户购买行为、商品销售趋势和用户画像。
1.2 数据源说明
|----------|------|---------|---------------------------------------------------|
| 数据源 | 文件格式 | 记录数 | 主要字段 |
| 阿里母婴数据集 | JSON | 953条 | user_id, birthday, gender |
| 天池母婴交易历史 | JSON | 29,971条 | user_id, auction_id, cat_id, cat1, buy_mount, day |
1.3 技术栈
-
数据存储: HDFS + Hive
-
计算引擎: Spark on Hive | MapReduce
-
数据同步: DataX
-
可视化: Quick BI + 花生壳(内网穿透)
-
数据库: MySQL (用于报表存储)
二、数据仓库建模
2.1 整体架构图

2.2 各层设计理念
ODS层(Operation Data Store)
设计理念: 贴源设计,保持数据原貌
存储策略: 按日期分区,存储原始JSON数据
目标: 支持数据回溯,减少对源系统的依赖
数据位置:/mum_baby/ods/ods_mum_baby/(sample)sam_tianchi_mum_baby.txt
/mum_baby/ods/ods_trade_history/(sample)sam_tianchi_mum_baby_trade_history.txt
DWD层(Data Warehouse Detail)设计理念: 维度建模,构建事实表和维度表
存储策略: ORC + Snappy压缩,按日期分区
目标: 提供干净的、可复用的明细数据
数据位置:/mum_baby/dwd/dwd_mum_baby
/mum_baby/dwd/dwd_trade
DIM层(Dimension)
设计理念: 一致性维度,维度规范化
存储策略: 全量存储,缓慢变化维处理
目标: 提供统一的维度视图,支持多维度分析
数据位置:/mum_baby/dim/dim_category
/mum_baby/dim/dim_user_baby
DWS层(Data Warehouse Service)
设计理念: 面向主题,轻度聚合
存储策略: ORC + Snappy压缩,按日期分区
目标: 提高查询性能,支持即席查询
数据位置:/mum_baby/dws/dws_user_trade_summary
/mum_baby/ads/ads_trade_by_baby_gender
ADS层(Application Data Store)
设计理念: 面向应用,指标计算
存储策略: 按业务需求分区
目标: 直接支持报表和可视化
数据位置:/mum_baby/ads/ads_trade_by_baby_gender
/mum_baby/ads/ads_trade_by_category
2.3 数据表结构设计
2.3.1 ODS层表结构
-
ods_mum_baby (母婴用户原始数据表)
CREATE EXTERNAL TABLE ods_mum_baby (
line STRING COMMENT '原始JSON数据'
)
LOCATION '/mum_baby/ods/ods_mum_baby';
设计思路: 直接存储原始JSON,便于数据追溯和重新处理。
-
ods_trade_history (交易历史原始数据表)
CREATE EXTERNAL TABLE ods_trade_history (
line STRING COMMENT '原始JSON数据'
)
LOCATION '/mum_baby/ods/ods_trade_history';
设计思路: 与母婴用户表设计一致,保持原始数据完整性。
2.3.2 DWD层表结构
-
dwd_mum_baby (母婴用户明细表)
CREATE EXTERNAL TABLE dwd_mum_baby (
user_id BIGINT COMMENT '用户ID',
birthday STRING COMMENT '儿童生日(yyyyMMdd)',
gender INT COMMENT '性别:0-女,1-男,2-未知'
)
PARTITIONED BY (dt STRING COMMENT '分区日期')
STORED AS ORC
LOCATION '/mum_baby/dwd/dwd_mum_baby'
TBLPROPERTIES ("orc.compress"="snappy");
设计思路:
-
从JSON解析出结构化字段
-
按dt分区便于数据管理
-
使用ORC+Snappy压缩提高存储和查询效率
-
dwd_trade (交易明细事实表)
CREATE EXTERNAL TABLE dwd_trade (
user_id BIGINT COMMENT '用户ID',
auction_id BIGINT COMMENT '商品ID',
cat_id BIGINT COMMENT '类目ID',
cat1 BIGINT COMMENT '一级类目ID',
property STRING COMMENT '商品属性',
buy_mount BIGINT COMMENT '购买数量',
day STRING COMMENT '购买日期(yyyyMMdd)'
)
PARTITIONED BY (dt STRING COMMENT '分区日期')
STORED AS ORC
LOCATION '/mum_baby/dwd/dwd_trade'
TBLPROPERTIES ("orc.compress"="snappy");
设计思路:
-
这是核心事实表,记录每笔交易详情
-
包含了用户、商品、类目、时间等多个维度
-
按dt(交易日期)分区,支持时间范围查询优化
2.3.3 DIM层表结构
-
dim_user_baby (用户-儿童维度表)
CREATE EXTERNAL TABLE dim_user_baby (
user_id BIGINT COMMENT '用户ID',
birthday STRING COMMENT '儿童生日',
gender INT COMMENT '性别',
age_year INT COMMENT '年龄(年)'
)
STORED AS ORC
LOCATION '/mum_baby/dim/dim_user_baby'
TBLPROPERTIES ("orc.compress"="snappy");
维度设计思路:
-
用户ID: 维度主键,连接事实表
-
儿童生日: 可用于计算儿童年龄,分析不同年龄段消费特征
-
性别: 重要分析维度,分析男女童消费差异
-
年龄: 衍生维度,按年龄段分组分析
-
dim_category (商品类目维度表)
CREATE EXTERNAL TABLE dim_category (
cat_id BIGINT COMMENT '类目ID',
cat1 BIGINT COMMENT '一级类目ID'
)
STORED AS ORC
LOCATION '/mum_baby/dim/dim_category'
TBLPROPERTIES ("orc.compress"="snappy");
维度设计思路:
-
类目层级: 支持类目层级分析(cat1为一级类目)
-
叶子类目标识: 区分是否是最终商品类目,便于不同粒度分析
-
可扩展性: 可扩展为多级类目(cat2, cat3等)
-
dim_date (时间维度表)
CREATE EXTERNAL TABLE dim_date (
date_id STRING COMMENT '日期 yyyyMMdd',
week_id STRING COMMENT '周',
week_day STRING COMMENT '星期几',
day STRING COMMENT '日',
month STRING COMMENT '月',
quarter STRING COMMENT '季度',
year STRING COMMENT '年',
is_workday STRING COMMENT '是否工作日'
)
LOCATION '/mum_baby/dim/dim_date';
维度设计思路:
-
多层次时间粒度: 支持年、月、日、季度、周等多个时间维度分析
-
业务属性: 包含is_weekend等业务相关属性
-
完整性: 为所有交易日期生成记录,确保时间维度完整性
2.3.4 DWS层表结构
-
dws_user_summary (用户交易汇总宽表)
CREATE EXTERNAL TABLE dws_user_summary (
user_id BIGINT COMMENT '用户ID',
total_trades BIGINT COMMENT '总交易次数',
total_amount BIGINT COMMENT '总购买数量',
avg_amount DOUBLE COMMENT '平均购买量',
first_trade STRING COMMENT '首次交易日期',
last_trade STRING COMMENT '最近交易日期'
)
PARTITIONED BY (dt STRING COMMENT '统计日期')
STORED AS ORC
LOCATION '/mum_baby/dws/dws_user_summary'
TBLPROPERTIES ("orc.compress"="snappy");
设计思路:
-
用户粒度: 按用户聚合,形成用户画像基础
-
交易行为: 汇总交易次数、总购买量等核心指标
-
时间特征: 记录首次和最近交易,分析用户生命周期
2.3.5 ADS层表结构(export)
PS:这里由于刚开始在传ads层格式错误导致后面无法上传数据,所以直接新建了export层作为ads层
-
ads_trade_by_baby_gender (按儿童性别统计表)
CREATE EXTERNAL TABLE ads_trade_by_baby_gender (
gender INT COMMENT '性别',
trade_count BIGINT COMMENT '交易次数',
total_amount BIGINT COMMENT '总购买数量',
dt STRING COMMENT '统计日期'
)
LOCATION '/mum_baby/export/ads_trade_by_baby_gender';
设计思路:
-
核心维度: 性别是最基本的用户画像维度
-
多重指标: 包含交易次数、购买数量、日期等多个角度
-
应用导向: 直接支持"性别消费分布"可视化图表
-
ads_trade_by_category (按商品类目统计表)
CREATE EXTERNAL TABLE ads_trade_by_category (
cat1 BIGINT COMMENT '一级类目',
trade_count BIGINT COMMENT '交易次数',
total_amount BIGINT COMMENT '总购买数量',
dt STRING COMMENT '统计日期'
)
LOCATION '/mum_baby/export/ads_trade_by_category';
设计思路:
-
业务维度: 类目是电商核心分析维度
-
销售分析: 从交易次数、销售数量、两个维度分析
-
应用导向: 支持"热门类目排行榜"等可视化需求
-
dws_user_summary (用户交易汇总宽表)
CREATE EXTERNAL TABLE IF NOT EXISTS dws_user_summary_text (
user_id BIGINT,
total_trades BIGINT,
total_amount BIGINT,
avg_amount DOUBLE,
first_trade STRING,
last_trade STRING,
dt STRING
)
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'
LOCATION '/mum_baby/export/dws_user_summary';
设计思路:
-
用户粒度: 按用户聚合,形成用户画像基础
-
交易行为: 汇总交易次数、总购买量等核心指标
-
时间特征: 记录首次和最近交易,分析用户生命周期
三、数据仓库建设
3.1 设置动态分区
SET hive.exec.dynamic.partition=true;
SET hive.exec.dynamic.partition.mode=nonstrict;
3.2 从 ODS 到 DWD 的母婴信息表
INSERT OVERWRITE TABLE dwd_mum_baby PARTITION (dt)
SELECT
get_json_object(line, '$.user_id'),
get_json_object(line, '$.birthday'),
get_json_object(line, '$.gender')
FROM ods_mum_baby
WHERE line IS NOT NULL;
3.2 从 ODS 到 DWD 的交易表(按 day 分区)
INSERT OVERWRITE TABLE dwd_trade PARTITION (dt)
SELECT
get_json_object(line, '$.user_id'),
get_json_object(line, '$.auction_id'),
get_json_object(line, '$.cat_id'),
get_json_object(line, '$.cat1'),
get_json_object(line, '$.property'),
get_json_object(line, '$.buy_mount'),
get_json_object(line, '$.day'),
get_json_object(line, '$.day') AS dt
FROM ods_trade_history
WHERE line IS NOT NULL;
3.3 创建用户维度表
INSERT OVERWRITE TABLE dim_user_baby
SELECT
user_id,
birthday,
gender,
floor(datediff(current_date(), to_date(birthday, 'yyyyMMdd')) / 365) AS age_year
FROM dwd_mum_baby
WHERE birthday IS NOT NULL;
3.4 创建类目维度表
INSERT OVERWRITE TABLE dim_category
SELECT DISTINCT
cat_id,
cat1
FROM dwd_trade
WHERE cat_id IS NOT NULL;
3.5 在ads层中将ORC格式转为文本文件
-- 1. 导出按性别统计表
INSERT OVERWRITE DIRECTORY '/mum_baby/export/ads_trade_by_baby_gender'
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'
STORED AS TEXTFILE
SELECT gender, trade_count, total_amount, dt
FROM ads_trade_by_baby_gender;
-- 2. 导出按类目统计表
INSERT OVERWRITE DIRECTORY '/mum_baby/export/ads_trade_by_category'
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'
STORED AS TEXTFILE
SELECT cat1, trade_count, total_amount, dt
FROM ads_trade_by_category;
-- 插入数据到宽表
INSERT OVERWRITE TABLE dws_user_summary_text
SELECT
user_id,
COUNT(*) AS total_trades,
SUM(buy_mount) AS total_amount,
ROUND(AVG(CAST(buy_mount AS DOUBLE)), 2) AS avg_amount,
MIN(day) AS first_trade,
MAX(day) AS last_trade,
'sample' AS dt
FROM dwd_trade
GROUP BY user_id;
四、数据可视化
4.1 在MySQL中创建目标表
-- MySQL数据库:mum_baby
USE mum_baby;
-- 1. 按儿童性别统计表
CREATE TABLE `ads_trade_by_baby_gender` (
`gender` int DEFAULT NULL COMMENT '性别:0-女,1-男,2-未知',
`trade_count` bigint DEFAULT NULL COMMENT '交易次数',
`total_amount` bigint DEFAULT NULL COMMENT '总购买数量',
`dt` varchar(20) DEFAULT NULL COMMENT '统计日期'
)
ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='按儿童性别统计交易';
-- 2. 按商品类目统计表
CREATE TABLE `ads_trade_by_category` (
`cat1` bigint DEFAULT NULL COMMENT '一级类目',
`trade_count` bigint DEFAULT NULL COMMENT '交易次数',
`total_amount` bigint DEFAULT NULL COMMENT '总购买数量',
`dt` varchar(20) DEFAULT NULL COMMENT '统计日期'
)
ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='按商品类目统计交易';
-- 3. 用户交易汇总表(新增)
CREATE TABLE `dws_user_summary` (
`user_id` bigint DEFAULT NULL COMMENT '用户ID',
`total_trades` bigint DEFAULT NULL COMMENT '总交易次数',
`total_amount` bigint DEFAULT NULL COMMENT '总购买数量',
`avg_amount` double DEFAULT NULL COMMENT '平均购买量',
`first_trade` varchar(20) DEFAULT NULL COMMENT '首次交易日期',
`last_trade` varchar(20) DEFAULT NULL COMMENT '最近交易日期',
`dt` varchar(20) DEFAULT NULL COMMENT '统计日期'
)
ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户交易汇总';
4.2 DataX配置文件
4.2.1 ads_trade_by_baby_gender.json (从HDFS导出到MySQL)
{
"job": {
"setting": {
"speed": {
"channel": 1
}
},
"content": [
{
"reader": {
"name": "hdfsreader",
"parameter": {
"path": "/mum_baby/export/ads_trade_by_gender/*",
"defaultFS": "hdfs://10.10.10.102:8020",
"column": [
{
"index": 0,
"type": "long"
},
{
"index": 1,
"type": "long"
},
{
"index": 2,
"type": "long"
},
{
"index": 3,
"type": "string"
}
],
"fileType": "text",
"encoding": "UTF-8",
"fieldDelimiter": "\t"
}
},
"writer": {
"name": "mysqlwriter",
"parameter": {
"writeMode": "replace",
"username": "root",
"password": "DataX@123456",
"column": [
"gender",
"trade_count",
"total_amount",
"dt"
],
"session": [
"set session sql_mode='ANSI'"
],
"preSql": [
"truncate table ads_trade_by_baby_gender"
],
"connection": [
{
"jdbcUrl": "jdbc:mysql://10.10.10.102:3306/mum_baby?useUnicode=true&characterEncoding=utf-8&useSSL=false",
"table": [
"ads_trade_by_baby_gender"
]
}
]
}
}
}
]
}
}
4.2.2 ads_trade_by_category.json
{
"job": {
"setting": {
"speed": {
"channel": 1
}
},
"content": [
{
"reader": {
"name": "hdfsreader",
"parameter": {
"path": "/mum_baby/export/ads_trade_by_category/*",
"defaultFS": "hdfs://10.10.10.102:8020",
"column": [
{
"index": 0,
"type": "long"
},
{
"index": 1,
"type": "long"
},
{
"index": 2,
"type": "long"
},
{
"index": 3,
"type": "string"
}
],
"fileType": "text",
"encoding": "UTF-8",
"fieldDelimiter": "\t"
}
},
"writer": {
"name": "mysqlwriter",
"parameter": {
"writeMode": "replace",
"username": "root",
"password": "DataX@123456",
"column": [
"cat1",
"trade_count",
"total_amount",
"dt"
],
"session": [
"set session sql_mode='ANSI'"
],
"preSql": [
"truncate table ads_trade_by_category"
],
"connection": [
{
"jdbcUrl": "jdbc:mysql://10.10.10.102:3306/mum_baby?useUnicode=true&characterEncoding=utf-8&useSSL=false",
"table": [
"ads_trade_by_category"
]
}
]
}
}
}
]
}
}
4.2.3 dws_user_summary.json
{
"job": {
"setting": {
"speed": {
"channel": 1
}
},
"content": [
{
"reader": {
"name": "hdfsreader",
"parameter": {
"path": "/mum_baby/export/dws_user_summary/*",
"defaultFS": "hdfs://10.10.10.102:8020",
"column": [
{
"index": 0,
"type": "long"
},
{
"index": 1,
"type": "long"
},
{
"index": 2,
"type": "long"
},
{
"index": 3,
"type": "double"
},
{
"index": 4,
"type": "string"
},
{
"index": 5,
"type": "string"
},
{
"index": 6,
"type": "string"
}
],
"fileType": "text",
"encoding": "UTF-8",
"fieldDelimiter": "\t"
}
},
"writer": {
"name": "mysqlwriter",
"parameter": {
"writeMode": "replace",
"username": "root",
"password": "DataX@123456",
"column": [
"user_id",
"total_trades",
"total_amount",
"avg_amount",
"first_trade",
"last_trade",
"dt"
],
"session": [
"set session sql_mode='ANSI'"
],
"preSql": [
"truncate table dws_user_summary"
],
"connection": [
{
"jdbcUrl": "jdbc:mysql://10.10.10.102:3306/mum_baby?useUnicode=true&characterEncoding=utf8&useSSL=false",
"table": [
"dws_user_summary"
]
}
]
}
}
}
]
}
}
五、项目展示

六、项目出现的问题记录【附】
6.1 项目过程
经历了四次建表建仓,如图,只有最后一次成功了,接下来将说一下具体过程:

6.1.1 第一次尝试失败总结
在第一次进行传输时,其实是最后有导出数据,但是由于建表到建仓在insert那里有许多数据没有传上去,导致最后只有一个总体记录的数据集,如下:

只能做出来一个卡片,先大体跑了一遍流程,唯一的卡片如下:

6.1.2 第二次尝试失败总结
第一次做出来一个卡片我想让ai帮忙再多一些数据,但是没想到直接给我新建仓了
在进行第二次传数据时,但是由于是以数组存放json文件的,再加上之前还加了一些字段,导致过程中出现了很多很多问题,尤其有的数据传成功了,有的不行,导致整个人很乱,于是第二次也告别失败了
6.1.3 第三次尝试失败总结
这次先用python把csv文件转成了json文件,不添加任何字段,去掉了json文件的 '[' , ']',然后做到一半看到了老师发过来处理好的json格式的文档,于是怕自己转的有问题,果断放弃当前自己处理的数据,用老师的文档做,才有了后面第四次的成功
6.2 项目踩坑记录
6.2.1 建表
先说建表,本以为用ai生成就行,然后历经几次失败,才发现是需要知道我要什么数据才行,还是需要了解里面的每一个字段是具体什么用处的才行,建表也得从简单的做起,不能直接暴力的把两个表直接合并在一起,还是得微微的熟悉下
6.2.2 日志
主要也是在第一次尝试的时候,想贴合已有的jar包,在用python把csv文件转成json文件还多加了一些字段,导致最后把自己都绕进去了,于是就放弃了这个想法
所以关于日志这个事情,我想自己用java写怕来不及,主要也是ai也不是很可靠,我怕出现我搞不定的错误,一直卡在那里没有进度,所以暂时搁置了,只是想先从简单的做起,然后有了这次经验之后再慢慢深入了解
6.2.3 数据仓库建设
其实在hive里面建表还是很好建表的,关键是插入这个数据就显得问题很多了,归根究底前几次都是因为json格式导致最后数据经常失败,才不太顺利
6.2.4 mysql
这里由于最开始我的c盘,d盘满了,中间用一天时间清理c盘,d盘,上传几十个G的文件,导致没有听mysql的登录密码是root,然后问ai找那个初始化密码,发现有的时候登录成功,有的时候登录不成功,改密码那里也是卡了一段时间,改无密码登录,然后后面发现无密码登录之后他会觉得不安全,会限制你,然后把密码重置了一遍'DataX@123456'才把密码改过来的
6.2.5 DataX配置文件
然后中间DataX的配置文件那里,首先就是要保证字段中不能用int类型,之前就是这个问题,只能把int改成long,然后就是路径,ip和当前ip是否一致等
6.3 心得体会
总而言之,做完发现也不是很复杂,但是细节很多,还是要先把基础掌握好,再深入去了解
虽然很基础,做出来也是普通的样子,但是还是很有成就感