实训4 ETL构建中间层

《豆瓣图书数据ETL实训手册》

------基于MySQL的中间层数据构建与清洗实践


一、实训目标

本实训通过真实场景中的图书数据(豆瓣图书表 book_info),完成以下核心任务:

  1. 构建中间层表 ,作为数据清洗与转换的桥梁;
    保护原始数据不被破坏
    统一杂乱数据格式
    清洗脏数据与异常值
    降低上层分析难度
  2. 实现 ETL 抽取(Extract)、转换(Transform)、加载(Load) 流程;
  3. 解决字段拆分中的常见问题,如:
  4. 多个分隔符 / 的嵌套结构;
  5. 价格字段含非数字字符(如"元");
  6. 掌握正则表达式、字符串函数、类型转换、空值处理等核心技术。

二、背景与数据说明

数据来源

  • 表名:doubanbooks.book_info
  • 字段说明:
字段名 类型 说明
id INT 图书唯一标识
book_name VARCHAR(500) 书名
book_info TEXT 信息字段,内容整合了作者、出版社、出版日期、价格等,格式为:作者 / 出版社 / 出版日期 / 价格

✅示例数据:

"余华 / 北京十月文艺出版社 / 2018-05-01 / 39.00元"


三、实训环境准备

软件与工具要求

项目 要求
数据库系统 MySQL 8.0+
客户端工具 Navicat / DBeaver / MySQL Workbench / 命令行
权限要求 CREATE, INSERT, SELECT, DROP 权限
表空间 已创建数据库 doubanbooks

💡 提示:若未创建数据库,请先执行:

sql 复制代码
CREATE DATABASE IF NOT EXISTS doubanbooks CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE doubanbooks;

四、操作步骤详解


步骤 1:创建中间层表

目的:保留原始字段 + 新增拆分后的独立字段,便于后续分析与建模。

sql 复制代码
-- 1. 删除已存在的中间表(防止冲突)
DROP TABLE IF EXISTS doubanbooks.book_info_mid;

-- 2. 创建中间层表
CREATE TABLE doubanbooks.book_info_mid (
    id INT PRIMARY KEY,
    book_name VARCHAR(500) NOT NULL,
    book_info TEXT NOT NULL,
    author VARCHAR(255),
    publisher VARCHAR(255),
    publish_date VARCHAR(50),
    price DECIMAL(10,2)
);

⚠️ 说明:

  • id 为主键,保证唯一;
  • book_name 保留原文;
  • book_info 保留原始字符串用于溯源;
  • author, publisher, publish_date, price 分别为拆分后字段;
  • price 使用 DECIMAL(10,2) 精确存储金额。

步骤 2:ETL 抽取、拆分与插入

核心逻辑 :从 book_info 中提取并清洗信息,使用 SUBSTRING_INDEXREGEXP_REPLACE 实现拆分与数据清洗。

sql 复制代码
INSERT INTO doubanbooks.book_info_mid (
    id, book_name, book_info,
    author, publisher, publish_date, price
)
SELECT
    id,
    book_name,
    book_info,

    -- 拆分作者(倒数第3个/前的内容)
    TRIM(
        SUBSTRING_INDEX(
            book_info,
            '/',
            LENGTH(book_info) - LENGTH(REPLACE(book_info, '/', '')) - 2
        )
    ) AS author,

    -- 拆分出版社(倒数第3段,取第一部分)
    TRIM(
        SUBSTRING_INDEX(
            SUBSTRING_INDEX(book_info, '/', -3),
            '/',
            1
        )
    ) AS publisher,

    -- 拆分出版日期(倒数第2段,取第一部分)
    TRIM(
        SUBSTRING_INDEX(
            SUBSTRING_INDEX(book_info, '/', -2),
            '/',
            1
        )
    ) AS publish_date,

    -- 修复价格:移除"元"字符,过滤非数字,强制转为小数
    CAST(
        NULLIF(
            TRIM(
                REGEXP_REPLACE(
                    REPLACE(SUBSTRING_INDEX(book_info, '/', -1), '元', ''),
                    '[^0-9.]', ''
                )
            ),
            ''
        ) AS DECIMAL(10,2)
    ) AS price

FROM doubanbooks.book_info;

拆解关键函数说明

函数 作用说明
REPLACE(str, old, new) 替换"元"为""(空),便于清洗
REGEXP_REPLACE(str, pattern, replacement) 使用正则删除所有非数字和小数点字符
SUBSTRING_INDEX(str, delim, n) 按分隔符分割字符串,取第 n 项(支持负数,从右数)
LENGTH(str) - LENGTH(REPLACE(str, '/', '')) 计算"/"的数量
TRIM() 去除首尾空格
NULLIF(expr, '') 若字段为空字符串,则返回 NULL,避免插入空值
CAST(... AS DECIMAL(10,2)) 强制类型转换,保证价格数据一致性

步骤 3:验证中间层结果

sql 复制代码
-- 查看前10条数据,确认清洗效果
SELECT * FROM doubanbooks.book_info_mid LIMIT 10;
预期输出样例:
id book_name book_info author publisher publish_date price
1 《活着》 余华 / 北京十月文艺出版社 / 2018-05-01 / 39.00元 余华 北京十月文艺出版社 2018-05-01 39.00
2 《许三观卖血记》 余华 / 上海文艺出版社 / 2012-07-01 / 45.00元 余华 上海文艺出版社 2012-07-01 45.00

✅ 检查点:

  • 所有字段均拆分正确;
  • price 无"元"符号,为标准小数;
  • NULLIF 保证了异常值(如空价格)被安全处理;
  • 所有字段不为空(即未插入非法值)。

五、异常处理与调试技巧

常见错误及解决方案

问题 原因 解决方案
Error: Cannot convert string to decimal 价格字段含"元"或特殊符号(如"¥"、空格) 使用 REPLACE + REGEXP_REPLACE 清洗
结果有 NULL 但应有值 分隔符不一致(如没有 / 或多余空格) 使用 TRIM + LENGTH(REPLACE(...)) 优化计数逻辑
拆分不准确(如作者错位) SUBSTRING_INDEX 偏移错误 通过观察原始数据确定层级,例如 -2 是日期,-1 是价格
数据类型转换失败 输入内容为纯字母或 NaN NULLIF(..., '') 防止空字符串传入 CAST

六、后续建议(进阶拓展)

  1. 数据质量检查脚本(增加)

    sql 复制代码
    -- 统计各字段空值情况
    SELECT 
        SUM(CASE WHEN author IS NULL THEN 1 ELSE 0 END) AS null_author,
        SUM(CASE WHEN publisher IS NULL THEN 1 ELSE 0 END) AS null_publisher,
        SUM(CASE WHEN publish_date IS NULL THEN 1 ELSE 0 END) AS null_publish_date,
        SUM(CASE WHEN price IS NULL THEN 1 ELSE 0 END) AS null_price
    FROM doubanbooks.book_info_mid;
  2. 创建索引提升查询效率

    sql 复制代码
    CREATE INDEX idx_publisher ON doubanbooks.book_info_mid(publisher);
    CREATE INDEX idx_price ON doubanbooks.book_info_mid(price);
  3. 构建维度表(用于BI分析)

    • dim_book:book_id, book_name, author, publish_date
    • dim_publisher:publisher_name, region
    • fact_book_sales:book_id, price, sales_count

附:完整SQL脚本包

将以下内容保存为 .sql 文件,可直接运行。

sql 复制代码
-- ===============================
-- 豆瓣图书ETL实训完整脚本
-- 作者:实训指导组
-- 时间:2025年4月
-- ===============================

-- 1. 创建数据库(如未存在)
CREATE DATABASE IF NOT EXISTS doubanbooks CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE doubanbooks;

-- 2. 创建中间层表
DROP TABLE IF EXISTS book_info_mid;
CREATE TABLE book_info_mid (
    id INT PRIMARY KEY,
    book_name VARCHAR(500) NOT NULL,
    book_info TEXT NOT NULL,
    author VARCHAR(255),
    publisher VARCHAR(255),
    publish_date VARCHAR(50),
    price DECIMAL(10,2)
);

-- 3. ETL抽取与加载
INSERT INTO book_info_mid (
    id, book_name, book_info,
    author, publisher, publish_date, price
)
SELECT
    id,
    book_name,
    book_info,

    TRIM(
        SUBSTRING_INDEX(
            book_info,
            '/',
            LENGTH(book_info) - LENGTH(REPLACE(book_info, '/', '')) - 2
        )
    ) AS author,

    TRIM(
        SUBSTRING_INDEX(
            SUBSTRING_INDEX(book_info, '/', -3),
            '/',
            1
        )
    ) AS publisher,

    TRIM(
        SUBSTRING_INDEX(
            SUBSTRING_INDEX(book_info, '/', -2),
            '/',
            1
        )
    ) AS publish_date,

    CAST(
        NULLIF(
            TRIM(
                REGEXP_REPLACE(
                    REPLACE(SUBSTRING_INDEX(book_info, '/', -1), '元', ''),
                    '[^0-9.]', ''
                )
            ),
            ''
        ) AS DECIMAL(10,2)
    ) AS price

FROM book_info;

-- 4. 查看结果
SELECT * FROM book_info_mid LIMIT 10;

-- 5. (可选)数据质量检查
SELECT 
    SUM(CASE WHEN author IS NULL THEN 1 ELSE 0 END) AS null_author,
    SUM(CASE WHEN publisher IS NULL THEN 1 ELSE 0 END) AS null_publisher,
    SUM(CASE WHEN publish_date IS NULL THEN 1 ELSE 0 END) AS null_publish_date,
    SUM(CASE WHEN price IS NULL THEN 1 ELSE 0 END) AS null_price
FROM book_info_mid;

结语

通过本实训,你已掌握了从原始杂乱数据 → 结构化中间表的全过程,是构建企业级数据仓库的重要基石。

相关推荐
哥本哈士奇1 天前
数据仓库笔记 第五篇:Data Mart 层(数据集市)
数据仓库
juniperhan1 天前
Flink 系列第18篇:Flink 动态表、连续查询与 Changelog 机制
java·大数据·数据仓库·分布式·flink
juniperhan1 天前
Flink 系列第19篇:深入理解 Flink SQL 的时间语义与时区处理:从原理到实战
java·大数据·数据仓库·分布式·sql·flink
哥本哈士奇2 天前
数据仓库笔记 第三篇:常用缓慢变化维处理方式介绍
数据仓库
哥本哈士奇2 天前
数据仓库笔记 第一篇:数据仓库的定义、历史与意义
数据仓库
哥本哈士奇2 天前
数据仓库笔记 第四篇:Star Schema 层(维度建模)
数据仓库
RestCloud2 天前
零售行业全渠道数据整合:ETL工具如何支撑精准营销?
数据仓库·etl·零售·数据处理·数据集成·数据传输·数据同步
哥本哈士奇2 天前
数据仓库笔记 第二篇:PSA 层(持久化暂存区)详解
数据仓库
juniperhan3 天前
Flink 系列第17篇:Flink Table&SQL 核心概念、原理与实战详解
大数据·数据仓库·分布式·sql·flink
QEasyCloud20223 天前
企业数据仓库建设实践与价值分析
数据仓库