一、项目介绍

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");

    设计思路:

    1. 从JSON解析出结构化字段

    2. 按dt分区便于数据管理

    3. 使用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");

    设计思路:

    1. 这是核心事实表,记录每笔交易详情

    2. 包含了用户、商品、类目、时间等多个维度

    3. 按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");

    维度设计思路:

    1. 用户ID: 维度主键,连接事实表

    2. 儿童生日: 可用于计算儿童年龄,分析不同年龄段消费特征

    3. 性别: 重要分析维度,分析男女童消费差异

    4. 年龄: 衍生维度,按年龄段分组分析

    • 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");

    维度设计思路:

    1. 类目层级: 支持类目层级分析(cat1为一级类目)

    2. 叶子类目标识: 区分是否是最终商品类目,便于不同粒度分析

    3. 可扩展性: 可扩展为多级类目(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';

    维度设计思路:

    1. 多层次时间粒度: 支持年、月、日、季度、周等多个时间维度分析

    2. 业务属性: 包含is_weekend等业务相关属性

    3. 完整性: 为所有交易日期生成记录,确保时间维度完整性

    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");

    设计思路:

    1. 用户粒度: 按用户聚合,形成用户画像基础

    2. 交易行为: 汇总交易次数、总购买量等核心指标

    3. 时间特征: 记录首次和最近交易,分析用户生命周期

    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';

    设计思路:

    1. 核心维度: 性别是最基本的用户画像维度

    2. 多重指标: 包含交易次数、购买数量、日期等多个角度

    3. 应用导向: 直接支持"性别消费分布"可视化图表

    • 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';

    设计思路:

    1. 业务维度: 类目是电商核心分析维度

    2. 销售分析: 从交易次数、销售数量、两个维度分析

    3. 应用导向: 支持"热门类目排行榜"等可视化需求

    • 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';

    设计思路:

    1. 用户粒度: 按用户聚合,形成用户画像基础

    2. 交易行为: 汇总交易次数、总购买量等核心指标

    3. 时间特征: 记录首次和最近交易,分析用户生命周期

    三、数据仓库建设

    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 心得体会

    总而言之,做完发现也不是很复杂,但是细节很多,还是要先把基础掌握好,再深入去了解

    虽然很基础,做出来也是普通的样子,但是还是很有成就感

    更多推荐