大数据-249 离线数仓 - 电商分析 Hive 数仓实战:订单拉链表到 DWS 宽表设计与加载脚本详解

TL;DR

  • 场景:用 Hive 构建离线数仓,处理订单主表拉链、订单明细聚合与 DWS 宽表生成。
  • 结论:订单主表适合按创建日分区 + 拉链管理,订单商品表走常规事实处理,DWS 再做轻聚合与维度退化。
  • 产出:给出 DWD/DWS 表设计、分层职责、装载脚本思路,以及一份错误速查卡。

基本介绍

要处理的表有两张:订单表、订单产品表:

  • 订单表是周期性事实表,为保留订单状态,可以使用拉链表进行处理
  • 订单产品表普通的事实表,用常规的方法进行处理(如果有数据清洗、数据转换的需求 ODS=>DWD。如果没有数据清洗、数据转换的需求,保留在ODS,不做任何变化)

订单状态:

  • -3 用户拒收
  • -2 未付款订单
  • -1 用户取消
  • 0 等待发货
  • 1 配送中
  • 2 用户确认收货

订单从创建到最终完成,是有时间限制的,业务上也不允许订单一个月之后,订单状态仍然在发生变化。

DWD 层的定位

DWD 层可以理解为数仓的"细化层"或"明细层",其核心作用是将原始数据从 ODS 层向更高质量、更具业务价值的方向转化。具体定位如下:

  • 处于 数仓分层体系 的中间部分。
  • 对 ODS 层 的数据进行业务逻辑处理、数据清洗、去重、规范化等操作,形成细化的业务事实表。
  • 为 DWS 层 和其他上层应用提供标准化的数据源。

DWD 层的特点

  • 细粒度:数据保留了明细级别信息,通常是事实表,每条记录与业务事件或过程直接对应。
  • 业务逻辑清晰:通过一定的逻辑处理,明确数据的业务含义,数据更加贴近真实业务。
  • 清洗规范:对数据进行清洗、校验和补充,确保数据质量(如去重、格式化)。
  • 冗余优化:降低原始数据中的冗余度,只保留对业务分析有价值的信息。
  • 可溯源性:保留明细数据,以支持对问题的深入追溯分析。

DWD 层的常见处理逻辑

在构建 DWD 层时,通常会执行以下关键步骤:

数据清洗

  • 去重:去除重复记录,确保数据唯一性。
  • 异常处理:处理缺失值、异常值等数据质量问题。
  • 格式标准化:统一时间格式、数值单位等。

数据补充

  • 添加维度信息:通过与维度表关联补充更多业务相关字段。
  • 填充缺失值:使用默认值或业务规则填充数据。

业务逻辑处理

  • 数据分层:根据业务模块分组存储(如用户行为、商品信息、订单数据等)。
  • 数据解耦:将复杂的业务逻辑分解成单独的表。

历史快照

  • 有时需要保留数据的历史版本(比如每日商品价格快照)。

WD 层的作用

DWD 层的建立有助于提高整个数仓的规范性和可用性:

  • 保证数据质量:通过清洗和转换,过滤掉 ODS 中的无效数据,确保上游数据的准确性。
  • 便于分析建模:提供标准化、明细化的数据供 DWS 层建模和分析使用。
  • 支持灵活查询:DWD 层数据粒度细,方便灵活查询和支持多样化的业务需求。
  • 缩短分析链路:减少 DWS 和应用层的数据处理负担。

DWD层建表

在大数据领域,DWD 是 Data Warehouse Detail 的缩写,属于数仓分层体系中的一部分。它是数仓中数据模型设计的重要环节,通常位于 ODS(Operational Data Store,操作数据层) 和 DWS(Data Warehouse Summary,数据汇总层) 之间。DWD 层的主要作用是对 ODS 层的数据进行清洗、规范化、细化,生成具备事实意义的明细数据,为后续的数据分析和建模提供支持。

  • 与维表不同,订单事实表的记录数非常多
  • 订单有生命周期,订单的状态不可能永远处于变化中(订单的生命周期一般在15天左右)
  • 订单是一个链表,而且是分区表
  • 分区的目的:订单一旦中止,不会重复计算
  • 分区的条件:订单创建日期,保证相同的订单在同一个分区
sql 复制代码
-- 订单事实表(拉链表)
DROP TABLE IF EXISTS dwd.dwd_trade_orders;
create table dwd.dwd_trade_orders(
  `orderId` int,
  `orderNo` string,
  `userId` bigint,
  `status` tinyint,
  `productMoney` decimal,
  `totalMoney` decimal,
  `payMethod` tinyint,
  `isPay` tinyint,
  `areaId` int,
  `tradeSrc` tinyint,
  `tradeType` int,
  `isRefund` tinyint,
  `dataFlag` tinyint,
  `createTime` string,
  `payTime` string,
  `modifiedTime` string,
  `start_date` string,
  `end_date` string
) COMMENT '订单事实拉链表'
partitioned by (dt string)
STORED AS PARQUET;

DWD层数据加载

编写一个脚本来进行处理:

shell 复制代码
vim dwd_load_trade_orders.sh

编写的内容如下所示:

shell 复制代码
#!/bin/bash
source /etc/profile
if [ -n "$1" ]
then
do_date=$1
else
do_date=`date -d "-1 day" +%F`
fi
sql="
set hive.exec.dynamic.partition.mode=nonstrict;
set hive.exec.dynamic.partition=true;
INSERT OVERWRITE TABLE dwd.dwd_trade_orders
partition(dt)
SELECT orderId,
orderNo,
userId,
status,
productMoney,
totalMoney,
payMethod,
isPay,
areaId,
tradeSrc,
tradeType,
isRefund,
dataFlag,
createTime,
payTime,
modifiedTime,
case when modifiedTime is not null
then from_unixtime(unix_timestamp(modifiedTime,
'yyyy-MM-dd HH:mm:ss'),'yyyy-MM-dd')
else from_unixtime(unix_timestamp(createTime,
'yyyy-MM-dd HH:mm:ss'), 'yyyy-MM-dd')
end as start_date,
'9999-12-31' as end_date,
from_unixtime(unix_timestamp(createTime, 'yyyy-MM-dd
HH:mm:ss'), 'yyyy-MM-dd') as dt
FROM ods.ods_trade_orders
WHERE dt='$do_date'
union all
SELECT A.orderId,
A.orderNo,
A.userId,
A.status,
A.productMoney,
A.totalMoney,
A.payMethod,
A.isPay,
A.areaId,
A.tradeSrc,
A.tradeType,
A.isRefund,
A.dataFlag,
A.createTime,
A.payTime,
A.modifiedTime,
A.start_date,
CASE WHEN B.orderid IS NOT NULL AND A.end_date >
'$do_date'
THEN date_add('$do_date', -1)
ELSE A.end_date END AS end_date,
from_unixtime(unix_timestamp(A.createTime, 'yyyy-MM-dd
HH:mm:ss'), 'yyyy-MM-dd') as dt
FROM (SELECT * FROM dwd.dwd_trade_orders WHERE
dt>date_add('$do_date', -15)) A
left outer join (SELECT * FROM ods.ods_trade_orders
WHERE dt='$do_date') B
ON A.orderId = B.orderId;
"
hive -e "$sql"

DWS层建表及数据加载

DIM、DWD => 数据仓库分层、数据仓库理论 需求:计算当天

  • 全国所有订单信息
  • 全国、一级商品分类订单信息
  • 全国、耳机商品分类订单信息
  • 大区所有订单信息
  • 大区、一级商品分类订单信息
  • 大区、二级商品分类订单信息
  • 城市所有订单信息
  • 城市、一级商品分类订单信息
  • 城市、二级商品分类订单信息

需要的信息:订单表、订单商品表、商品信息维表、商品分类维表、商品地域维表:

  • 订单表:订单ID、订单状态
  • 订单商品表:订单ID、商品ID、商家ID、单价、数量
  • 商品信息维表:商品ID、三级分类ID
  • 商品分类维表:一级名称、一级分类ID、二级名称、二级分类ID、三级名称、三级分类ID
  • 商家地域维表:商家ID、区域名称、区域ID、城市名称、城市ID

订单表、订单商品表、商品信息表:订单ID、商品ID、商家ID、三级分类ID、单价、数量(订单明细表) 订单明细表、商品分类维表、商家地域维表:订单ID、商品ID、商家ID、三级分类名称、三级分类名称、三级分类名称、单价、数量、区域、城市 => 订单明细宽表

DWS层建表

dws_trade_orders (订单明细)由以下表轻微聚合而成:

  • dwd.dwd_trade_orders(拉链表、分区表)
  • ods.ods_trade_order_product(分区表)
  • dim.dim_trade_product_info(维表、拉链表)

dws_trade_orders_w(订单明细宽表)由以下表组成:

  • ads.dws_trade_orders(分区表)
  • dim.dim_trade_product_act(分区表)
  • dim.dim_trade_shops_org(分区表)
sql 复制代码
-- 订单明细表(轻度汇总事实表)。每笔订单的明细
DROP TABLE IF EXISTS dws.dws_trade_orders;
create table if not exists dws.dws_trade_orders(
  orderid string, -- 订单id
  cat_3rd_id string, -- 商品三级分类id
  shopid string, -- 店铺id
  paymethod tinyint, -- 支付方式
  productsnum bigint, -- 商品数量
  paymoney double, -- 订单商品明细金额
  paytime string -- 订单时间
)
partitioned by (dt string)
STORED AS PARQUET;
-- 订单明细表宽表
DROP TABLE IF EXISTS dws.dws_trade_orders_w;
create table if not exists dws.dws_trade_orders_w(
  orderid string, -- 订单id
  cat_3rd_id string, -- 商品三级分类id
  thirdname string, -- 商品三级分类名称
  secondname string, -- 商品二级分类名称
  firstname string, -- 商品一级分类名称
  shopid string, -- 店铺id
  shopname string, -- 店铺名
  regionname string, -- 店铺所在大区
  cityname string, -- 店铺所在城市
  paymethod tinyint, -- 支付方式
  productsnum bigint, -- 商品数量
  paymoney double, -- 订单明细金额
  paytime string -- 订单时间
)
partitioned by (dt string)
STORED AS PARQUET;

DWS层加载数据

shell 复制代码
vim dws_load_trade_orders.sh

写入的内容如下所示:

sql 复制代码
#!/bin/bash
source /etc/profile
if [ -n "$1" ]
then
do_date=$1
else
do_date=`date -d "-1 day" +%F`
fi
sql="
insert overwrite table dws.dws_trade_orders
partition(dt='$do_date')
select t1.orderid as orderid,
t3.categoryid as cat_3rd_id,
t3.shopid as shopid,
t1.paymethod as paymethod,
t2.productnum as productsnum,
t2.productnum*t2.productprice as pay_money,
t1.paytime as paytime
from (select orderid, paymethod, paytime
      from dwd.dwd_trade_orders
      where dt='$do_date') T1
left join
(select orderid, productid, productnum, productprice
 from ods.ods_trade_order_product
 where dt='$do_date') T2
on t1.orderid = t2.orderid
left join
(select productid, shopid, categoryid
 from dim.dim_trade_product_info
 where start_dt <= '$do_date'
 and end_dt >= '$do_date' ) T3
on t2.productid=t3.productid;
insert overwrite table dws.dws_trade_orders_w
partition(dt='$do_date')
select t1.orderid,
t1.cat_3rd_id,
t2.thirdname,
t2.secondname,
t2.firstname,
t1.shopid,
t3.shopname,
t3.regionname,
t3.cityname,
t1.paymethod,
t1.productsnum,
t1.paymoney,
t1.paytime
from (select orderid,
      cat_3rd_id,
      shopid,
      paymethod,
      productsnum,
      paymoney,
      paytime
      from dws.dws_trade_orders
      where dt='$do_date') T1
join
(select thirdid, thirdname, secondid, secondname,
 firstid, firstname
 from dim.dim_trade_product_cat
 where dt='$do_date') T2
on T1.cat_3rd_id = T2.thirdid
join
(select shopid, shopname, regionname, cityname
 from dim.dim_trade_shops_org
 where dt='$do_date') T3
on T1.shopid = T3.shopid
"
hive -e "$sql"
  • dwd.dwd_trade_orders(拉链表、分区表)
  • ods.ods_trade_order_product(分区表)
  • dim.dim_trade_product_info(维表、拉链表)
  • dim.dim_trade_product_cat(分区表)
  • dim.dim_trade_shops_org(分区表)

错误速查

症状 根因定位 修复
Shell 脚本执行直接报错 #!/bin/bash 使用了全角感叹号 查看脚本首行改为 #!/bin/bash
Hive SQL 解析失败 SQL 中存在多余换行、逗号或字段别名不一致 先单独 hive -e 执行 SQL清理换行拼接问题,统一字段命名
拉链表 end_date 更新异常 仅按 orderId 左连接,未严格区分"新状态覆盖旧状态"的更新条件 检查 join 后的 B.orderId is not null 命中情况明确新增、更新、未变化三类数据逻辑
历史订单被漏算 只回扫近 15 天分区,但真实业务状态变化超过 15 天 抽查超周期订单将回扫窗口与业务 SLA 对齐,或增加异常补偿机制
DWD 读取结果不对 where dt='$do_date' 只取当天分区,可能拿不到当前有效订单快照 对比拉链表 start_date/end_date查询时按有效期过滤,不要只按分区日期取数
DWS 明细金额不准 productnum * productprice 未考虑优惠、退款、运费分摊 对账订单支付金额与明细金额明确金额口径:原价、实付、优惠后金额分别建字段
宽表关联后数据变少 使用 join 而非 left join,维表缺失导致事实被过滤 对比 join 前后记录数维表不完备时优先 left join,并补默认维度值
表名引用混乱 文中写了 ads.dws_trade_orders,应为 dws.dws_trade_orders 检查 DWS 宽表来源表统一库名,避免 ADS/DWS 混用
字段名不一致导致报错 如 pay_money 与 paymoney、productnum 与 productsnum 混用 检查建表与查询字段统一命名风格,一处定义,全链路一致
维表快照时间不一致 有的维表按拉链有效期过滤,有的按 dt='$do_date' 过滤 检查各维表时间字段统一维度取数口径:日快照或拉链有效期二选一
订单状态口径混乱 未限制是否只统计已支付/已完成订单 检查 DWS 装载 where 条件明确状态过滤规则,例如只保留已支付订单
分区策略效果一般 拉链表本质关注有效期,但当前按创建日分区会增加跨期维护复杂度 看更新脚本扫描路径评估是否保留创建日分区,或改为快照式/增量式策略

其他系列

🚀 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案例 详解

相关推荐
Lalolander2 小时前
生产计划频繁被打乱?利用MES构建动态排程与订单影响分析体系
大数据·人工智能·mes·制造执行系统·工单管理软件·工厂管理软件·生产计划管理
忆~遂愿2 小时前
摆脱浏览器书签混乱!Fenrus+cpolar解锁公网访问新玩法
大数据
IT_陈寒2 小时前
用Python爬虫抓了100万条数据后,我总结了这5个反封禁技巧
前端·人工智能·后端
bug攻城狮2 小时前
SpringBoot 脚手架搭建指南:从零构建企业级开发框架
java·spring boot·后端·架构·系统架构·设计规范
IvanCodes2 小时前
三、Kafka安装详细教程
大数据·分布式·kafka
雷焰财经2 小时前
破解差异化转型之困:从宇信科技“双龙头”项目看其全栈赋能之道
大数据·人工智能·科技
人道领域2 小时前
【苍穹外卖】深度解析:商品浏览四大核心接口设计(附完整数据流转图)
java·数据库·后端·sql
程序员爱钓鱼2 小时前
Go静态资源嵌入方案: embed包深度解析
后端·面试·go
AskHarries2 小时前
独立开发者最浪费时间的10件事
后端·ai编程