暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

从ODS到ADS:一条SQL的数据奇幻漂流与层层加工之旅

陈乔数据观止 2025-09-03
109

大家好,我是陈乔怀古。

资深数据仓库工程师,捣鼓大数据、数据仓库和数据治理,分享路上的“坑”与“果”,用实战经验,助你少走弯路,共同成长。

添加v:cqhg_bigdata,备注数仓/大数据/数据治理/AI大模型,领取对应资料。

后台发送“资料”获取更多大数据资源。


送你一张优惠券👇

以典型四层数据架构为例

  1. ODS(操作数据存储):接近源系统的数据层,保持数据原始状态
  2. DWD(数据仓库明细层):进行数据清洗、标准化和维度退化
  3. DWS(数据仓库汇总层):按主题域构建的轻度汇总层
  4. ADS(应用数据服务):面向具体应用的数据集,高度聚合

起点:ODS层的原始数据

旅程从ODS层开始,这里存储着从业务系统同步的原始数据。假设有一个电商平台,ODS层包含以下关键表:

-- ODS层订单表(原始数据)
-- 存储从业务系统直接同步的原始订单数据,保持数据原貌
CREATE TABLE ods.orders (
    order_id BIGINT,                    -- 订单ID,唯一标识符
    user_id BIGINT,                     -- 用户ID,关联用户表
    total_amount DECIMAL(10,2),         -- 订单总金额
    status INT,                         -- 订单状态(原始编码)
    create_time TIMESTAMP,              -- 订单创建时间
    update_time TIMESTAMP,              -- 订单更新时间
    -- 其他原始字段...
    data_source STRING COMMENT '数据来源',  -- 数据来源系统标识
    extract_time TIMESTAMP COMMENT '抽取时间'-- 数据抽取到ODS的时间
);

-- ODS层用户表
-- 存储用户基本信息,从用户管理系统同步
CREATE TABLE ods.users (
    user_id BIGINT,                     -- 用户ID,主键
    username STRING,                    -- 用户名
    registration_date TIMESTAMP,        -- 注册日期
    last_login_time TIMESTAMP,          -- 最后登录时间
    -- 其他原始字段...
    data_source STRING,                 -- 数据来源
    extract_time TIMESTAMP              -- 抽取时间
);

-- ODS层商品表
-- 存储商品基本信息,从商品管理系统同步
CREATE TABLE ods.products (
    product_id BIGINT,                  -- 商品ID,主键
    product_name STRING,                -- 商品名称
    category_id INT,                    -- 商品类目ID
    price DECIMAL(10,2),                -- 商品价格
    -- 其他原始字段...
    data_source STRING,                 -- 数据来源
    extract_time TIMESTAMP              -- 抽取时间
);

第一站:DWD层的数据清洗与标准化

在DWD层,对原始数据进行清洗、标准化和维度退化处理:

-- 创建DWD层订单明细宽表
-- 将多个ODS表关联,进行数据清洗、标准化和维度退化
CREATE TABLE dwd.fact_order_detail AS
SELECT
    -- 订单维度字段
    o.order_id,                         -- 订单ID
    o.user_id,                          -- 用户ID
    o.total_amount,                     -- 订单总金额(已清洗)
    
    -- 订单状态标准化:将数字状态码转换为可读文本
    CASE
        WHEN o.status = THEN '待付款'
        WHEN o.status = THEN '已付款'
        WHEN o.status = THEN '已发货'
        WHEN o.status = THEN '已完成'
        WHEN o.status = THEN '已取消'
        ELSE '未知状态'                   -- 处理异常状态
    END AS order_status,
    
    DATE(o.create_time) AS order_date,  -- 订单日期(去除时间部分)
    o.create_time AS order_time,        -- 订单完整时间戳
    
    -- 用户维度字段(从用户表关联获取)
    u.username,                         -- 用户名
    
    -- 用户类型分类:基于注册时间与订单时间的差值
    CASE
        WHEN DATEDIFF(o.create_time, u.registration_date) <= 30 THEN'新用户'
        WHEN DATEDIFF(o.create_time, u.registration_date) <= 365 THEN'活跃用户'
        ELSE'老用户'
    ENDAS user_type,
    
    -- 时间维度字段(用于时间分区和分析)
    YEAR(o.create_time) AS year,        -- 年份
    MONTH(o.create_time) AS month,      -- 月份
    DAY(o.create_time) AS day,          -- 日期
    HOUR(o.create_time) AS hour,        -- 小时
    
    -- 数据质量监控字段
    CASE
        WHEN o.total_amount < THEN 1-- 标记金额为负的异常数据
        ELSE 0
    END AS is_amount_negative,
    
    CASE
        WHEN o.user_id IS NULL THEN 1   -- 标记用户ID缺失的数据
        ELSE 0
    END AS is_user_missing,
    
    -- 数据版本控制字段
    o.update_time,                      -- 源系统更新时间
    CURRENT_TIMESTAMP AS dwd_etl_time   -- DWD层ETL处理时间
    
FROM ods.orders o
LEFT JOIN ods.users u ON o.user_id = u.user_id  -- 左连接用户表,保留没有用户信息的订单

-- 数据过滤条件
WHERE o.create_time >= '2023-01-01'      -- 时间范围过滤:只处理2023年后的数据
AND o.total_amount >= 0                -- 合理性过滤:排除负金额订单
AND o.status IN (1,2,3,4,5);           -- 有效性过滤:只处理已知状态订单

第二站:DWS层的轻度汇总

在DWS层,按主题域进行数据轻度汇总:

-- 创建DWS层用户行为日汇总表
-- 按用户和日期维度进行轻度汇总,便于后续分析
CREATE TABLE dws.user_behavior_daily AS
SELECT
    user_id,                            -- 用户ID
    order_date,                         -- 订单日期
    
    -- 基础订单指标
    COUNT(DISTINCT order_id) AS daily_order_count,          -- 日订单数(去重)
    SUM(total_amount) AS daily_order_amount,                -- 日订单总金额
    AVG(total_amount) AS avg_order_amount,                  -- 平均订单金额
    MAX(total_amount) AS max_order_amount,                  -- 最大订单金额
    MIN(total_amount) AS min_order_amount,                  -- 最小订单金额
    
    -- 使用窗口函数计算7日移动平均
    AVG(SUM(total_amount)) OVER (
        PARTITION BY user_id                               -- 按用户分区
        ORDER BY order_date                                -- 按日期排序
        ROWS BETWEEN PRECEDING AND CURRENT ROW           -- 取最近7天(包括当天)
    ) AS weekly_avg_amount,
    
    -- 用户行为分析指标
    COUNT(CASE WHEN order_status = '已付款' THEN order_id ENDAS paid_order_count,      -- 已付款订单数
    COUNT(CASE WHEN order_status = '已取消' THEN order_id ENDAS canceled_order_count,  -- 已取消订单数
    
    -- 用户价值分层(基于日消费金额)
    CASE
        WHEN SUM(total_amount) > 1000 THEN '高价值用户'
        WHEN SUM(total_amount) > 500 THEN '中价值用户'
        WHEN SUM(total_amount) > 100 THEN '低价值用户'
        ELSE '普通用户'
    END AS user_value_segment,
    
    -- 用户活跃度分析
    DATEDIFF(MAX(order_date), MIN(order_date)) AS user_active_span,  -- 用户活跃时间跨度(天)
    COUNT(DISTINCT order_date) AS user_active_days,                  -- 用户活跃天数
    
    CURRENT_TIMESTAMPAS dws_etl_time                                -- DWS层ETL处理时间

FROM dwd.fact_order_detail
WHERE order_date >= DATE_SUB(CURRENT_DATE365)  -- 只处理最近一年的数据
GROUP BY user_id, order_date                     -- 按用户和日期分组
HAVING daily_order_count > 0;                    -- 过滤掉没有订单的日期

终点站:ADS层的应用数据服务

最终,为特定应用场景构建高度聚合的ADS表:

-- 创建ADS层用户画像表
-- 为业务应用提供完整的用户画像数据
CREATE TABLE ads.user_profile AS

-- 用户行为基础统计
WITH user_behavior AS (
    SELECT
        user_id,
        COUNT(*) AS total_orders,                -- 总订单数
        SUM(daily_order_amount) AS total_amount, -- 总消费金额
        MAX(daily_order_amount) AS max_daily_amount,  -- 单日最高消费
        AVG(daily_order_amount) AS avg_daily_amount,  -- 日均消费
        SUM(daily_order_count) AS total_order_count,  -- 总订单数(另一种计算方式)
        SUM(paid_order_count) AS total_paid_orders,   -- 总付款订单数
        SUM(canceled_order_count) AS total_canceled_orders,  -- 总取消订单数
        MAX(user_active_days) AS active_days,         -- 活跃天数
        MAX(user_active_span) AS active_span,         -- 活跃时间跨度
        MAX(user_value_segment) AS value_segment      -- 价值分层
    FROM dws.user_behavior_daily
    GROUP BY user_id
),

-- 用户最近购买行为分析
user_recency AS (
    SELECT
        user_id,
        DATEDIFF(CURRENT_DATEMAX(order_date)) AS days_since_last_order,  -- 距上次购买天数
        NTILE(5OVER (ORDER BY DATEDIFF(CURRENT_DATEMAX(order_date))) AS recency_segment  -- 最近度五分位
    FROM dwd.fact_order_detail
    GROUP BY user_id
),

-- 用户购买频率分析
user_frequency AS (
    SELECT
        user_id,
        COUNT(DISTINCT order_date) AS order_frequency,  -- 购买频率(下单天数)
        NTILE(5OVER (ORDER BY COUNT(DISTINCT order_date)) AS frequency_segment  -- 频率五分位
    FROM dwd.fact_order_detail
    GROUP BY user_id
),

-- 用户购买金额分析
user_monetary AS (
    SELECT
        user_id,
        SUM(total_amount) AS monetary_value,            -- 总消费金额
        NTILE(5OVER (ORDER BY SUM(total_amount)) AS monetary_segment  -- 金额五分位
    FROM dwd.fact_order_detail
    GROUP BY user_id
),

-- RFM综合分析
rfm_analysis AS (
    SELECT
        r.user_id,
        r.recency_segment,      -- 最近度分段
        f.frequency_segment,    -- 频率分段
        m.monetary_segment,     -- 金额分段
        
        -- RFM用户分群
        CASE
            WHEN r.recency_segment >= AND f.frequency_segment >= AND m.monetary_segment >= THEN '重要价值用户'
            WHEN r.recency_segment >= AND f.frequency_segment >= AND m.monetary_segment >= THEN '重要发展用户'
            WHEN r.recency_segment >= AND f.frequency_segment >= AND m.monetary_segment >= THEN '一般保持用户'
            ELSE '一般发展用户'
        END AS rfm_segment
    FROM user_recency r
    JOIN user_frequency f ON r.user_id = f.user_id
    JOIN user_monetary m ON r.user_id = m.user_id
)

-- 最终用户画像数据
SELECT
    u.user_id,                          -- 用户ID
    u.username,                         -- 用户名
    u.registration_date,                -- 注册日期
    
    -- 用户行为指标
    ub.total_orders,                    -- 总订单数
    ub.total_amount,                    -- 总消费金额
    ub.avg_daily_amount,                -- 日均消费
    ub.total_paid_orders,               -- 付款订单数
    ub.total_canceled_orders,           -- 取消订单数
    ub.active_days,                     -- 活跃天数
    ub.active_span,                     -- 活跃时间跨度
    ub.value_segment,                   -- 价值分层
    
    -- 最近行为指标
    ur.days_since_last_order,           -- 距上次购买天数
    
    -- RFM分析结果
    rfm.recency_segment,                -- 最近度分段
    rfm.frequency_segment,              -- 频率分段
    rfm.monetary_segment,               -- 金额分段
    rfm.rfm_segment,                    -- RFM分群
    
    -- 用户生命周期状态判断
    CASE
        WHEN ur.days_since_last_order > 180 THEN '流失用户'
        WHEN ur.days_since_last_order > 90 THEN '濒临流失用户'
        WHEN ur.days_since_last_order > 30 THEN '一般活跃用户'
        ELSE '高活跃用户'
    END AS user_lifecycle_status,
    
    -- 用户价值预测
    CASE
        WHEN rfm.monetary_segment = AND rfm.frequency_segment >= THEN '超高价值用户'
        WHEN rfm.monetary_segment >= AND rfm.frequency_segment >= THEN '高价值用户'
        WHEN rfm.monetary_segment >= THEN '中价值用户'
        ELSE '一般价值用户'
    END AS user_value_prediction,
    
    -- 交叉销售潜力评估
    CASE
        WHEN ub.avg_daily_amount > 500 AND rfm.frequency_segment >= THEN '高交叉销售潜力'
        WHEN ub.avg_daily_amount > 200 AND rfm.frequency_segment >= THEN '中交叉销售潜力'
        ELSE '低交叉销售潜力'
    END AS cross_sell_potential,
    
    CURRENT_TIMESTAMP AS ads_etl_time   -- ADS层ETL处理时间

FROM ods.users u
LEFT JOIN user_behavior ub ON u.user_id = ub.user_id        -- 关联用户行为数据
LEFT JOIN user_recency ur ON u.user_id = ur.user_id         -- 关联最近行为数据
LEFT JOIN rfm_analysis rfm ON u.user_id = rfm.user_id       -- 关联RFM分析数据

WHERE u.registration_date >= '2020-01-01';  -- 只处理2020年后注册的用户

数据质量监控与治理

在整个数据流转过程中需要确保数据质量:

-- 数据质量监控SQL
-- 监控各数据层的数据质量指标
SELECT
    'DWD层'AS data_layer,               -- 数据层标识
    COUNT(*) AS total_records,           -- 总记录数
    SUM(is_amount_negative) AS negative_amount_count,  -- 负金额记录数
    SUM(is_user_missing) AS missing_user_count,        -- 缺失用户记录数
    COUNT(DISTINCT user_id) AS unique_users,           -- 唯一用户数
    MIN(order_date) AS earliest_date,    -- 最早日期
    MAX(order_date) AS latest_date       -- 最晚日期
FROM dwd.fact_order_detail

UNION ALL

-- DWS层数据质量监控
SELECT
    'DWS层'AS data_layer,
    COUNT(*) AS total_records,
    AS negative_amount_count,          -- DWS层已清洗,无负金额
    AS missing_user_count,             -- DWS层已清洗,无缺失用户
    COUNT(DISTINCT user_id) AS unique_users,
    MIN(order_date) AS earliest_date,
    MAX(order_date) AS latest_date
FROM dws.user_behavior_daily

UNION ALL

-- ADS层数据质量监控
SELECT
    'ADS层'AS data_layer,
    COUNT(*) AS total_records,
    AS negative_amount_count,          -- ADS层已高度聚合
    AS missing_user_count,             -- ADS层已高度聚合
    COUNT(DISTINCT user_id) AS unique_users,
    MIN(registration_date) AS earliest_date,  -- 最早注册日期
    MAX(registration_date) AS latest_date     -- 最晚注册日期
FROM ads.user_profile;

性能优化策略

在整个数据处理过程中采用多种性能优化策略:

  1. 分区策略:所有表都按时间分区
  2. 索引优化:对常用查询字段建立索引
  3. 数据压缩:使用列式存储和压缩算法
  4. 计算优化:使用窗口函数减少JOIN操作
-- 创建优化后的DWD表
-- 使用分区和列式存储优化性能
CREATE TABLE dwd.fact_order_detail_optimized (
    order_id BIGINT,                     -- 订单ID
    user_id BIGINT,                      -- 用户ID
    total_amount DECIMAL(10,2),          -- 订单金额
    order_status STRING,                 -- 订单状态
    order_date DATE,                     -- 订单日期
    -- 其他字段...
)
PARTITIONED BY (year INTmonth INT)     -- 按年月分区,提高查询性能
STORED AS ORC                            -- 使用ORC列式存储格式
TBLPROPERTIES ("orc.compress"="SNAPPY"); -- 使用SNAPPY压缩算法

-- 创建位图索引优化用户查询
-- 位图索引适合低基数字段,如状态字段
CREATE INDEX idx_user_id ON TABLE dwd.fact_order_detail_optimized(user_id) AS 'BITMAP';

总结:数据的价值升华

通过这条从ODS到ADS的数据奇幻漂流,我们见证了数据如何从原始的、杂乱的状态,经过层层加工和转化,最终成为支持业务决策的宝贵资产。这个过程不仅仅是技术实现,更是数据价值的升华:

  1. 从原始到精炼:数据经过清洗、标准化,变得可靠可用
  2. 从分散到整合:多源数据被整合为统一的视图
  3. 从历史到预测:从描述历史现象到预测未来趋势
  4. 从数据到洞察:最终转化为驱动业务增长的行动洞察

这条SQL的数据漂流之旅展示了现代数据架构的核心思想:通过分层处理和数据加工,将原始数据转化为真正的商业价值。


据统计,99%的大咖都关注了这个公众号👇

扫码加入星球🪐 所有资料都可以直接下载

猜你喜欢👇

  1. 别只回答“做什么”!新业务入仓,说清DWD/DWS的“建仓依据”才是加分项

  2. 主题域 vs 数据域:数仓设计不是重复造轮子,90%的人都理解错了!

  3. 教科书一般不教!关于数据仓库的30个冷知识,敢来测测吗?

  4. 性能直接炸了!玩转 Hive 分区与分桶,查询效率轻松翻数十倍!

  5. Hive优化十大法则:让慢查询从2小时降到5分钟的秘籍

  6. 数据仓库中的“一致性维度”是什么?为什么它能统一指标口径?(文末送福利)

  7. ADS层设计指南:面向业务的指标聚合艺术

  8. DWS层实战:宽表建模的10个经典场景!

  9. 宽表设计避坑指南:哪些字段该加?哪些不该加?

  10. 为什么你的DWD层总是混乱?维度建模三件套拯救你!

  11. 数据仓库分层设计:ODS/DWD/DWS/ADS到底该怎么划边界?

文章转载自陈乔数据观止,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论