《豆瓣图书数据ETL实训手册》
------基于MySQL的中间层数据构建与清洗实践
一、实训目标
本实训通过真实场景中的图书数据(豆瓣图书表 book_info),完成以下核心任务:
- 构建中间层表 ,作为数据清洗与转换的桥梁;
保护原始数据不被破坏
统一杂乱数据格式
清洗脏数据与异常值
降低上层分析难度 - 实现 ETL 抽取(Extract)、转换(Transform)、加载(Load) 流程;
- 解决字段拆分中的常见问题,如:
- 多个分隔符
/的嵌套结构; - 价格字段含非数字字符(如"元");
- 掌握正则表达式、字符串函数、类型转换、空值处理等核心技术。
二、背景与数据说明
数据来源
- 表名:
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_INDEX和REGEXP_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 |
六、后续建议(进阶拓展)
-
数据质量检查脚本(增加)
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; -
创建索引提升查询效率
sqlCREATE INDEX idx_publisher ON doubanbooks.book_info_mid(publisher); CREATE INDEX idx_price ON doubanbooks.book_info_mid(price); -
构建维度表(用于BI分析)
dim_book:book_id, book_name, author, publish_datedim_publisher:publisher_name, regionfact_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;
结语
通过本实训,你已掌握了从原始杂乱数据 → 结构化中间表的全过程,是构建企业级数据仓库的重要基石。