大数据-245 离线数仓 - 电商分析 Hive 拉链表入门实战:缓慢变化维 SCD 类型、建表加载与常见错误速查

TL;DR

  • 场景:离线数仓中,维表属性会缓慢变化,需要兼顾历史追溯与存储成本。
  • 结论:SCD 不是只等于拉链表;Hive 里拉链表更接近 SCD Type 2 的一种工程实现。
  • 产出:给出 SCD 类型梳理、Hive 建表与装载示例、版本矩阵与错误速查卡。

缓慢变化维

缓慢变化维(SCD,Slowly Changing Dimensions),在现实世界中,维度的属性随着时间的流失发生缓慢的变化(缓慢是相对事实表而言,事实表数据变化的速度比维度表快)。 处理维度表的历史变化信息的问题称为处理缓慢变化维的问题,简称SCD问题,处理缓慢变化维的方法有以下几种常见方式:

  • 保留原值
  • 直接覆盖
  • 增加新属性列
  • 快照表
  • 拉链表

缓慢变化维(slowly varying dimension)是数据仓库领域中维度数据的一种特性,用于描述随着时间推移,某些属性会发生变化的维度。然而,这些变化通常是相对缓慢和不频繁的,因此称之为"缓慢变化维"(SCD, Slowly Changing Dimensions)。处理缓慢变化维是数据仓库设计中的一个重要部分,因为它直接影响数据的历史记录保存和版本控制。

缓慢变化维主要用于记录维度的属性随时间变化的情况,例如客户的地址、雇员的职位或产品的价格等。在实际应用中,为了满足业务需求,通常会根据变化记录需求的不同,采用不同的技术实现。

缓慢变化维的类型

缓慢变化维常被分为以下几种类型:

SCD 类型 0:不处理变化

  • 特点:属性变化时,不记录变化,只保留最新值。
  • 适用场景:维度的属性对历史分析无影响。
  • 优点:实现简单。
  • 缺点:无法保存历史信息。

SCD 类型 1:覆盖变化

  • 特点:属性变化时,直接覆盖旧值。
  • 适用场景:只需要保留最新的维度信息,历史数据无关紧要。
  • 优点:占用存储空间小,查询效率高。
  • 缺点:无法追溯历史信息,丢失数据变更记录。
  • 示例:客户地址发生变化,仅更新地址字段。

SCD 类型 2:保留历史记录

  • 特点:为每一次变化创建一条新记录,同时可以通过标识字段或时间戳区分当前数据和历史数据。

实现方式:

  • 增加版本号:新增一个版本号字段表示记录版本。

  • 增加有效时间区间:新增开始和结束时间字段表示记录的有效期。

  • 适用场景:需要保留所有历史信息,支持基于时间的回溯查询。

  • 优点:完整记录历史变化。

  • 缺点:数据量增加,查询复杂度可能提高。

  • 示例:客户地址发生变化,保留旧地址记录,新建一条记录保存新地址。

SCD 类型 3:有限的历史记录

  • 特点:为变化的属性设置额外的字段,仅保留有限的历史信息(如最近一次变化)。
  • 适用场景:只需要保存一部分历史信息,对存储空间要求较低。
  • 优点:减少数据量,存储需求低。
  • 缺点:历史记录有限,无法满足更复杂的回溯分析。
  • 示例:添加"旧地址"和"当前地址"两个字段。

SCD 类型 4:历史表

  • 特点:将历史记录存储在单独的历史表中,主表中只保留当前数据。
  • 适用场景:需要完整保存历史信息,同时希望主表保持精简。
  • 优点:主表数据简单,查询当前值效率高。
  • 缺点:需要额外的历史表,查询历史信息时复杂度增加。
  • 示例:主表存储客户的最新地址,历史表存储地址变更记录。

SCD 类型 6:混合型

  • 特点:结合类型 1、2 和 3,既保留最新值,又保留有限的历史信息,还可以保存完整的历史记录。
  • 适用场景:需要兼顾历史记录和当前值查询效率。
  • 优点:兼具多种类型的优点。
  • 缺点:实现较为复杂。
  • 示例:主表记录最新信息,同时增加版本号和时间戳以追溯历史。

保留原始值

维度属性值不做更改,保留原始值。 如商品上架售卖时间:一个商品上架售卖后由于其他原因下架,后来又再次上架,此种情况产生了多个商品上架售卖时间,如果业务重点关注的是商品首次商家售卖时间,则采用该方式。

直接覆盖

修改维度属性为最新值,直接覆盖,不保留历史信息。 如商品属于哪个品类:当商品品类发生变化时,直接重写为商品类。

增加新属性列

在维度表中新增加新的一列,原先属性列存放上一版本的属性值,当前属性列存放当前版本属性值,还可以增加一列记录变化的事件。 缺点:只能记录最后一次变化的信息。

快照表

每天保留一份全量数据,简单高效。 缺点是信息重复,浪费磁盘空间。 适用的维表不能太大,但是使用场景多,范围广,一般而言维表都不会很大。

拉链表

拉链表适用于:表的数据量大,而且数据会发生新增和变化,但是大部分是不变的(数据发生变化的百分比不大),且是缓慢变化的(如电商用户信息表中的某些用户属性不可能每天都变化),主要目的是为了节省空间。

适用的场景:

  • 表的数据量大
  • 表中部分字段会被更新
  • 表中记录变量的比例不高
  • 需要保留历史信息

维表拉链表应用案例

创建表

注意:这里的操作是在Hive中 对应的SQL内容如下:

sql 复制代码
CREATE DATABASE test;
-- 用户信息
DROP TABLE IF EXISTS test.userinfo;
CREATE TABLE test.userinfo(
  userid STRING COMMENT '用户编号',
  mobile STRING COMMENT '手机号码',
  regdate STRING COMMENT '注册日期')
COMMENT '用户信息'
PARTITIONED BY (dt string)
row format delimited fields terminated by ',';

-- 拉链表(存放用户历史信息)
-- 拉链表不是分区表;多了两个字段start_date、end_date
DROP TABLE IF EXISTS test.userhis;
CREATE TABLE test.userhis(
  userid STRING COMMENT '用户编号',
  mobile STRING COMMENT '手机号码',
  regdate STRING COMMENT '注册日期',
  start_date STRING,
  end_date STRING)
COMMENT '用户信息拉链表'
row format delimited fields terminated by ',';

执行结果如下所示:

数据文件

shell 复制代码
vim /opt/wzk/userinfo.dat

写入的内容如下所示:

shell 复制代码
001,13551111111,2020-03-01,2020-06-20
002,13561111111,2020-04-01,2020-06-20
003,13571111111,2020-05-01,2020-06-20
004,13581111111,2020-06-01,2020-06-20
002,13562222222,2020-04-01,2020-06-21
004,13582222222,2020-06-01,2020-06-21
005,13552222222,2020-06-21,2020-06-21
004,13333333333,2020-06-01,2020-06-22
005,13533333333,2020-06-21,2020-06-22
006,13733333333,2020-06-22,2020-06-22
001,13554444444,2020-03-01,2020-06-23
003,13574444444,2020-05-01,2020-06-23
005,13555554444,2020-06-21,2020-06-23
007,18600744444,2020-06-23,2020-06-23
008,18600844444,2020-06-23,2020-06-23

写入的内容如下所示:

加载数据

sql 复制代码
-- 动态分区数据加载:分区的值由输入数据确定
-- 创建中间表(非分区表)
DROP TABLE IF EXISTS test.tmp1;
CREATE TABLE test.tmp1 AS
SELECT * FROM test.userinfo;

-- 设置 tmp1 非分区表的字段分隔符为 ','(原始默认分隔符为 '\001')
ALTER TABLE test.tmp1 SET SERDEPROPERTIES('field.delim' = ',');

-- 向中间表加载数据
LOAD DATA LOCAL INPATH '/opt/wzk/userinfo.dat' INTO TABLE test.tmp1;

-- 启用非严格模式的动态分区
SET hive.exec.dynamic.partition.mode = nonstrict;

-- 从中间表向分区表加载数据
INSERT INTO TABLE test.userinfo
PARTITION (dt)
SELECT * FROM test.tmp1;

执行结果如下图所示:

如果我们查询所有数据,可以看到对应的内容:

错误速查

症状 根因定位 修复
读者看完仍分不清 SCD 与拉链表 把"概念分类"和"工程实现"混写了 在正文中明确一句:拉链表通常是 SCD Type 2 的一种实现,不等于全部 SCD
SCD Type 0 与"保留原值"前后矛盾 前文把 Type 0 误写成"保留最新值" 对比"SCD 类型 0"与"保留原始值"两段把 Type 0 改为"不随源数据变化而更新,始终保留初始值"
"直接覆盖"段落里出现"重写为商品类"语义不顺 表述笔误 看"如商品属于哪个品类"示例改成"直接重写为新的商品品类"
"记录变量的比例不高"读起来不对 用词错误 看拉链表适用场景列表改成"记录变更的比例不高"
LOAD DATA 后字段错位或分隔失败 中间表默认 SerDe 与源文件分隔符不一致 SELECT * FROM test.tmp1 检查导入结果明确建 tmp1 的字段与分隔符,避免依赖 CTAS 后再改 SerDe
动态分区插入报错 未开启非严格动态分区或列顺序不匹配 检查 set hive.exec.dynamic.partition.mode 与 INSERT SQL保证最后一列对应分区字段 dt,并开启 nonstrict
查询到的数据不是"拉链表历史" 示例只完成了原始维表装载,没有真正做历史闭链 看 userhis 表是否有增量合并 SQL增补 userhis 的初始化和每日增量拉链 SQL,否则案例未闭环
读者误以为拉链表一定不能分区 把示例设计当成通用规范 看"拉链表不是分区表"一句改成"本文示例中未使用分区;生产环境是否分区取决于数据规模和查询模式"
start_date/end_date 设计有了,但不知道闭区间还是开区间 有效期口径未定义 看表结构和案例说明,未说明边界规则明确采用 [start_date, end_date] 还是 [start_date, end_date),并统一查询写法
历史查询结果重复或失真 未说明当前记录结束日期哨兵值 查看 userhis 设计,未给"当前有效记录"的结束日期规范约定当前记录 end_date='9999-12-31' 或业务最大日期
快照表与拉链表选型不清 没有给出成本/查询复杂度对比 看"快照表""拉链表"两节,描述偏概念增加一句选型规则:小表高可读性优先快照表,大表低变更且要追历史优先拉链表
文章能讲概念,但不够"落地" 缺少 userhis 初始化、更新、历史查询 SQL 看案例部分只到 userinfo 装载结束增加三段 SQL:初始化拉链、次日增量合并、按日期还原用户状态

其他系列

🚀 AI篇持续更新中(长期更新)

AI炼丹日志-29 - 字节跳动 DeerFlow 深度研究框斜体样式架 私有部署 测试上手 架构研究 ,持续打造实用AI工具指南! AI研究-132 Java 生态前沿 2025:Spring、Quarkus、GraalVM、CRaC 与云原生落地

💻 Java篇持续更新中(长期更新)

Java-218 RocketMQ Java API 实战:同步/异步 Producer 与 Pull/Push Consumer MyBatis 已完结,Spring 已完结,Nginx已完结,Tomcat已完结,分布式服务已完结,Dubbo已完结,MySQL已完结,MongoDB已完结,Neo4j已完结,FastDFS 已完结,OSS已完结,GuavaCache已完结,EVCache已完结,RabbitMQ已完结,RocketMQ正在更新... 深入浅出助你打牢基础!

📊 大数据板块已完成多项干货更新(300篇):

包括 Hadoop、Hive、Kafka、Flink、ClickHouse、Elasticsearch 等二十余项核心组件,覆盖离线+实时数仓全栈! 大数据-278 Spark MLib - 基础介绍 机器学习算法 梯度提升树 GBDT案例 详解

相关推荐
芝士爱知识a2 小时前
2026年 AI 期权工具全维度测评与推荐榜单:AlphaGBM 领跑,量化交易新范式
大数据·人工智能·python·ai量化·alphagbm·ai期权工具·ai期权工具推荐
天远Date Lab2 小时前
天远入职背调报告API对接实战:Python构建自动化背景调查中台
大数据·网络·python·自动化
huahailing10242 小时前
Spring Boot 异步事务最佳实践:TransactionTemplate 实战指南
数据库·spring boot·后端
Data_Journal2 小时前
如何将网站数据抓取到 Excel:一步步指南
大数据·开发语言·数据库·人工智能·php
小树苗m2 小时前
win11系统中安装openclaw、配置飞书及彻底卸载openclaw
大数据·飞书·openclaw·openclaw配置飞书·卸载openclaw·安装openclaw
二月夜2 小时前
记SpringBoot升级Tomcat引发的两类典型问题及解决方案
spring boot·后端·tomcat
神奇小汤圆2 小时前
面试官:什么是 fail-fast?什么是 fail-safe?
后端
忘机山人2 小时前
在 Kubernetes 上用 Fluent Bit 收集 Nginx 日志到 Easysearch
后端
鸿乃江边鸟2 小时前
Rust 的 mod(模块) 说明
开发语言·后端·rust