目录
[1.1 ADS层定位](#1.1 ADS层定位)
[1.2 设计特点](#1.2 设计特点)
[2.1 完整建表语句](#2.1 完整建表语句)
[2.2 字段设计解析](#2.2 字段设计解析)
[3.1 整体装载逻辑](#3.1 整体装载逻辑)
[3.2 UNION vs UNION ALL选择](#3.2 UNION vs UNION ALL选择)
[4.1 最近1天数据处理](#4.1 最近1天数据处理)
[4.2 最近7天和30天数据处理](#4.2 最近7天和30天数据处理)
[1. LATERAL VIEW EXPLODE()](#1. LATERAL VIEW EXPLODE())
[2. CASE WHEN表达式](#2. CASE WHEN表达式)
[3. COUNT(DISTINCT IF())](#3. COUNT(DISTINCT IF()))
[4.3 GROUP BY分组原理](#4.3 GROUP BY分组原理)
[5.1 存储优化](#5.1 存储优化)
[5.2 查询优化技巧](#5.2 查询优化技巧)
[6.1 可能的问题与解决方案](#6.1 可能的问题与解决方案)
[6.2 监控与维护](#6.2 监控与维护)
一、ADS层概述与应用场景
1.1 ADS层定位
ADS(Application Data Store)层是数据仓库的最后层,直接面向业务应用。它存储经过深度聚合和分析的数据,为报表、数据可视化、API服务等提供直接可用的数据。
1.2 设计特点
sql
-- 核心设计原则:
-- 1. 行式存储(TSV格式):便于数据导出到MySQL等关系型数据库
-- 2. GZIP压缩:在数据量不大时保证压缩效率
-- 3. 非分区设计:分析结果数据量通常较小
-- 4. 按需建表:客户需要什么字段就创建什么字段
二、建表语句深度解析
2.1 完整建表语句
sql
DROP TABLE IF EXISTS ads_order_stats_by_tm;
-- 使用EXTERNAL TABLE确保数据安全,删除表时不删除HDFS数据
CREATE EXTERNAL TABLE ads_order_stats_by_tm
(
`dt` STRING COMMENT '统计日期',
`recent_days` BIGINT COMMENT '最近天数,1:最近1天,7:最近7天,30:最近30天',
`tm_id` STRING COMMENT '品牌ID',
`tm_name` STRING COMMENT '品牌名称',
`order_count` BIGINT COMMENT '下单数',
`order_user_count` BIGINT COMMENT '下单人数'
) COMMENT '各品牌商品下单统计'
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t' -- TSV格式,制表符分隔
LOCATION '/warehouse/gmall/ads/ads_order_stats_by_tm/';
2.2 字段设计解析
| 字段名 | 数据类型 | 说明 | 设计考虑 |
|---|---|---|---|
| dt | STRING | 统计日期 | 固定日期格式,便于时间维度分析 |
| recent_days | BIGINT | 最近天数 | 支持多时间窗口分析(1/7/30天) |
| tm_id | STRING | 品牌ID | 业务主键,用于关联 |
| tm_name | STRING | 品牌名称 | 便于直接展示,减少关联查询 |
| order_count | BIGINT | 下单数 | 核心指标,统计订单数量 |
| order_user_count | BIGINT | 下单人数 | 统计独立用户数,反映用户覆盖 |
三、数据装载策略详解
3.1 整体装载逻辑
sql
-- 为什么采用"查询历史数据 + UNION + 新增数据"的模式?
insert overwrite table ads_order_stats_by_tm
select * from ads_order_stats_by_tm -- 步骤1:先查询历史所有数据
union -- 步骤2:UNION去重合并
-- 新增数据计算部分...
设计原因分析:
-
容错性考虑:脚本可能因资源不足失败,重新执行时需要保留历史数据
-
数据完整性:确保不会因部分失败导致数据丢失
-
幂等性设计:支持重复执行,结果一致
3.2 UNION vs UNION ALL选择
sql
-- 使用UNION而非UNION ALL的原因:
-- 1. UNION会自动去重,防止重复插入
-- 2. 当脚本重复执行时,避免生成重复记录
-- 3. 虽然性能稍差,但数据量小可接受
-- UNION ALL示例(不去重):
SELECT id FROM table1
UNION ALL -- 合并所有行,包括重复
SELECT id FROM table2;
-- UNION示例(去重):
SELECT id FROM table1
UNION -- 合并并去重
SELECT id FROM table2;
四、核心SQL代码逐行解析
4.1 最近1天数据处理
sql
select
1 recent_days, -- 固定值,表示统计最近1天
tm_id,
tm_name,
sum(order_count_1d) order_count, -- 聚合函数:对单日订单数求和
count(distinct(user_id)) order_user_count -- 去重统计独立用户数
from dws_trade_user_sku_order_1d
where dt='2022-06-08' -- 筛选特定日期
group by tm_id, tm_name -- 按品牌分组
关键函数解析:
-
SUM():聚合函数,计算总和
-
COUNT(DISTINCT):统计不重复值的数量
-
GROUP BY:分组子句,配合聚合函数使用
4.2 最近7天和30天数据处理
sql
select
recent_days,
tm_id,
tm_name,
sum(order_count), -- 对炸裂后的订单数求和
count(distinct(if(order_count>0,user_id,null))) -- 条件去重统计
from
(
select
recent_days,
user_id,
tm_id,
tm_name,
case recent_days -- CASE条件表达式
when 7 then order_count_7d
when 30 then order_count_30d
end order_count
from dws_trade_user_sku_order_nd
lateral view explode(array(7,30)) tmp as recent_days -- 炸裂函数
where dt='2022-06-08'
)t1
group by recent_days, tm_id, tm_name
高级函数详解:
1. LATERAL VIEW EXPLODE()
sql
-- 原始数据:一行包含7日和30日数据
-- 炸裂后:每个recent_days值生成一行
lateral view explode(array(7,30)) tmp as recent_days
-- 示例:
-- 原始:order_count_7d=100, order_count_30d=500
-- 炸裂后:
-- 行1: recent_days=7, order_count=100
-- 行2: recent_days=30, order_count=500
2. CASE WHEN表达式
sql
-- 根据条件返回不同值
case recent_days
when 7 then order_count_7d
when 30 then order_count_30d
else 0 -- 可选的默认值
end
-- 等同于:
if(recent_days=7, order_count_7d,
if(recent_days=30, order_count_30d, 0))
3. COUNT(DISTINCT IF())
sql
-- 条件去重统计
count(distinct(if(order_count>0, user_id, null)))
-- IF函数:条件成立返回user_id,否则返回null
-- COUNT不统计null值,实现只统计有订单的用户
4.3 GROUP BY分组原理
sql
-- 分组规则示例:
group by recent_days, tm_id, tm_name
-- 情况1:上下级关系(如省份-城市)
-- 以最细粒度(城市)为基准分组
group by city_id, province_name -- province_name只作为展示
-- 情况2:关联关系(如用户ID-用户名)
-- 以唯一标识(用户ID)为基准
group by user_id, user_name -- user_name不会影响分组
-- 情况3:无关联字段(如用户ID-商品ID)
-- 将两个字段组合作为分组键
group by user_id, product_id -- 每个组合唯一
五、性能优化与最佳实践
5.1 存储优化
sql
-- ADS层存储特点:
-- 1. 行式存储:适合频繁全表扫描的小数据量场景
-- 2. TSV格式:简单高效,兼容性好
-- 3. GZIP压缩:文本压缩率高,查询时自动解压
-- 4. 小文件合并:定期合并减少文件数量
5.2 查询优化技巧
sql
-- 1. 使用分区过滤(虽然ADS层通常不分区)
where dt='2022-06-08'
-- 2. 避免全表扫描
create index on ads_order_stats_by_tm(tm_id) -- 如果支持索引
-- 3. 合理使用统计信息
analyze table ads_order_stats_by_tm compute statistics;
六、扩展思考与改进建议
6.1 可能的问题与解决方案
| 问题 | 解决方案 | 代码示例 |
|---|---|---|
| 数据量增长 | 增加dt分区 | PARTITIONED BY (dt STRING) |
| 查询性能下降 | 增加索引或物化视图 | CREATE INDEX idx_tm ON ... |
| 数据重复风险 | 使用MERGE语句 | MERGE INTO ads_table USING ... |
6.2 监控与维护
sql
-- 1. 数据质量检查
select count(*) as total_rows,
count(distinct dt) as date_count,
min(dt) as earliest_date,
max(dt) as latest_date
from ads_order_stats_by_tm;
-- 2. 存储空间监控
hdfs dfs -du -h /warehouse/gmall/ads/ads_order_stats_by_tm/
-- 3. 数据新鲜度检查
select max(dt) as latest_load_time
from ads_order_stats_by_tm;
七、总结
ADS层作为数据仓库的最后一公里,其设计需要平衡多个因素:
-
业务友好性:字段命名直观,符合业务术语
-
性能考量:针对小数据量优化,快速响应查询
-
数据一致性:通过合理的装载策略确保数据准确
-
可维护性:代码清晰,便于理解和修改
通过本例的品牌商品下单统计表,我们可以看到如何将DWS层的明细数据转化为业务可用的聚合指标,这种模式在ADS层设计中具有典型性和代表性。