数据仓库分层建设全面指南

0、前言

场景:最近有求职者在面试中被问到数仓设计可不可以不分层?当求职者回答不可以的时候,面试官似乎对这一回答并不满意。

分析:针对该问题其实面试官并不是不懂数仓分层的意义,而是想要求职者讲明白什么样情况下需要分层,该如何分层,看看求职者是否真正对分层掌握,能在不同情形和环境下做出对策。数仓的面试其实就是一场思辨的过程,更像是哲学上的讨论,没有绝对的好坏,只有在某种场景、条件下的合不合适。

数据仓库是否分层取决于具体的业务场景、团队规模、数据复杂度以及维护需求。虽然分层设计有其显著优势,但在某些情况下,不分层的简化设计也可能是可行的。以下是更系统的分析

一、数仓分层目的 

分层(如ODS、DWD、DWS、ADS)的核心目标是:

  1. 解耦数据流:分离原始数据、清洗整合后的数据、聚合数据和应用数据。

  2. 提升复用性:中间层数据可被多个下游应用复用,减少重复计算。

  3. 优化性能:通过分层聚合减少复杂查询的计算量。

  4. 保障一致性:统一数据清洗和业务规则,避免口径混乱。

  5. 降低维护成本:问题定位和迭代更高效。

二、不分层的适用场景

适用场景说明
小型团队或简单业务●场景:数据量小(如日增量<GB级)、业务逻辑简单(如仅需几张报表)。
●优势:省去分层设计的开发和管理成本,快速交付。
●风险:业务扩展后可能面临重构压力。
实时数据流处理●场景:需要实时响应的场景(如监控、风控),数据直接从消息队列(如Kafka)写入OLAP引擎(如Doris)。
●方案:使用流式处理(Flink)+ 实时数仓(如ClickHouse),跳过传统分层。
探索性分析或临时需求●场景:临时数据探查、PoC验证,可直接在原始数据层(ODS)或数据湖中操作。
●工具:通过Trino、Spark SQL直接查询原始数据,快速输出结果。
现代架构的演进●Data Lakehouse:结合数据湖的灵活性和数仓的管理能力(如Delta Lake、Iceberg),部分场景可替代分层。
●Serverless查询引擎:如BigQuery、Snowflake,通过虚拟化层自动优化查询,降低对分层的依赖。

2.1 不分层潜在风险

风险说明
数据冗余与一致性●不同业务可能重复加工相同逻辑,导致资源浪费和结果不一致。
●案例:用户画像的“活跃用户”定义在A报表和B看板中不一致。
维护复杂度●业务逻辑分散在多个ETL任务或SQL脚本中,变更时需全网排查。
●统计:某电商未分层时,一个字段变更需修改12个任务,耗时3天;分层后仅需修改1个DWD表。
性能瓶颈●复杂查询直接扫描原始数据,可能导致计算时间过长。
●测试数据:某未分层日志分析系统,TOP10用户查询耗时120秒;分层后通过聚合层降至3秒。
数据质量风险●缺乏统一的清洗和稽核层,错误数据可能直接影响业务。
●案例:未清洗的订单数据中包含测试账号,导致GMV统计偏高。

三、数仓分层建设指南

数仓分层说明
ODSODS(Operation Data Store)操作数据层,即原始数据层,又叫贴源层,与业务系统基本同构(可能会增加管理字段),目的是保留历史,解耦业务数据库,这样整个数据平台只需要访问一次业务数据库即可。
所以ODS层存在的意义是尽可能减少对业务数据库的访问压力。ODS层有些时候会细分为两层,一个STG数据缓冲层,存原始数据,一个ODS,存简单清洗的数据。
DWDDWD(Data Warehouse Detail)层是数据仓库体系中的明细数据层,位于ODS(Operational Data Store,原始数据层)之上,DIM(维度层)和DWS(数据服务层)之下。它是数据仓库的核心加工区域,承担着将原始数据转换为可用分析数据的重要职责。
▶主要特点:
●面向主题:按业务领域进行数据组织
●粒度统一:保持相同业务过程的数据粒度一致
●结构规范:字段命名和定义遵循统一标准
●历史完整:保留历史变更数据,确保可追溯性
DWSDWS(Data Warehouse Service)层是数据仓库中的服务数据层,它基于DWD层的明细数据,按照业务主题对数据进行轻度汇总,形成主题宽表,主要包含了1d(最近1日)、nd(最近n日)、td(历史至今)等时间维度的指标,是数据仓库中承上启下的关键环节,既保证了数据的可复用性,又为上层应用提供了标准化的数据服务。
业务价值:
降低计算成本:预计算常用指标,避免重复计算
●统一指标口径:确保全公司指标计算规则统一
●提升分析效率:提供主题化的宽表,方便业务分析
●支持多维分析:保留完整维度,支持灵活查询
DIMDIM层,即dimension,维度,是连接数据仓库ODS原始数据与后续分析层的关键桥梁。
DIM层作为数据仓库的关键组成部分,其设计质量直接影响整个数据仓库的可用性和分析效率。
通过正确选择维度表类型、设计合理的更新策略,以及实施有效的优化措施,可以构建出高质量的维度层,为上层分析提供坚实基础。
标签层标签工厂生产的各种标签,用户标签、商品标签等等,一般在DWD层之上,互联网业务特有,现在也慢慢扩展到其他领域。
ADS层ADS(Application Data Store)层是数据仓库的最顶层,直接面向业务应用,为数据分析、报表展示和业务决策提供数据支持。它是数据仓库与业务系统之间的桥梁,将经过清洗、转换和汇总的数据以业务友好的方式呈现给最终用户。
▶应用场景:
●业务报表:为管理层提供决策支持的各类报表。
●运营分析:支持运营人员进行用户行为分析和营销效果评估。
●风险控制:提供风险监控和预警数据。
●客户服务:支持客服人员进行客户画像分析和精准服务。
●产品优化:为产品团队提供用户反馈和使用数据,指导产品迭代。
DM层DM层,又叫主题层,与主题域不一样,这是在企业级数仓之上,对某个单独业务或者部门专门设立的小型数据集市。DM层又可以根据业务需求再次拆分。

3.1 DWD层建设指南 

3.1.1 建设基本思路

1.业务分域设计

DWD层首先应该按照业务域(Domain)进行划分,常见的业务域包括:用户域:用户基础信息、注册、登录、地址等

  • 商品域:商品、类目、品牌等基础数据

  • 交易域:订单、支付、退款等交易过程数据

  • 流量域:点击、曝光、跳出等用户行为数据

  • 营销域:活动、优惠券、秒杀等营销数据

  • 库存域:库存、仓储等供应链数据

  • 互动域:评论、收藏、分享等社交数据

这种分域方式让数据架构更清晰,方便不同业务部门使用自己关心的数据。

2.明确数据模型类型

DWD层主要包含两类数据模型:

  • 事实表:记录业务事件,通常包含度量值和外键

  • 维度表:描述业务对象的属性,如用户、商品、时间等

根据数据更新方式,DWD表可以分为:

  • 全量表(Full):每次加载会覆盖所有历史数据

  • 增量表(Inc):只加载新增或变化的数据

  • 拉链表:记录数据的历史变更,保留所有版本信息

3.数据加工规范

DWD层的数据加工遵循以下规范:字段规范:统一字段命名和数据类型,如id、create_time等

  • 数据清洗:处理空值、异常值、重复值等

  • 数据转换:类型转换、编码转换、格式标准化

  • 数据整合:关联多个来源的数据,丰富信息维度

  • 指标计算:生成基础派生指标

4.分区与性能优化

为提高查询效率,DWD层通常采用分区策略:

  • 按时间分区:最常见的方式,如按天分区(k1字段)

  • 按业务分区:某些场景下按业务线或地区分区

  • 复合分区:时间+业务的组合分区策略

3.1.2 建设案例

 下面以dwd_user_address_full(用户地址表)表为例,详细说明DWD层表的设计和实现过程。

1.需求分析

用户地址信息是电商系统的重要基础数据,需要支持:

  • 用户历史地址查询

  • 配送范围分析

  • 区域销售分布统计

2.数据源分析

从ODS层,我们有两个相关表:

  • ods_user_address_full:用户地址基本信息

  • ods_base_province_full:省份信息

通过分析表结构,发现:

  • 用户地址表包含用户ID、省份ID等基本信息

  • 省份表包含省份名称、地区编码等信息

  • 缺少专门的城市和区县表

3.表结构设计

CREATE TABLE dwd.dwd_user_address_full
(
    `id` VARCHAR(255) COMMENT '地址ID',
    `k1` DATE COMMENT '数据日期',
    `user_id` STRING COMMENT '用户ID',
    `province_id` STRING COMMENT '省份ID',
    `province_name` STRING COMMENT '省份名称',
    `city_id` STRING COMMENT '城市ID',
    `city_name` STRING COMMENT '城市名称',
    `district_id` STRING COMMENT '区县ID',
    `district_name` STRING COMMENT '区县名称',
    `detail_address` STRING COMMENT '详细地址',
    `consignee` STRING COMMENT '收货人',
    `phone_num` STRING COMMENT '联系电话',
    `is_default` STRING COMMENT '是否默认地址',
    `create_time` DATETIME COMMENT '创建时间',
    `operate_time` DATETIME COMMENT '操作时间',
    `postal_code` STRING COMMENT '邮政编码',
    `full_address` STRING COMMENT '完整地址'
)
ENGINE=OLAP
UNIQUE KEY(`id`, `k1`)
DISTRIBUTED BY HASH(`id`);

4.ETL实现

-- 用户域用户地址全量表
INSERT INTO dwd.dwd_user_address_full(id, k1, user_id, province_id, province_name, 
city_id, city_name, district_id, district_name, detail_address, consignee, 
phone_num, is_default, create_time, operate_time, postal_code, full_address)
select
    ua.id,
    date('${pdate}') as k1,  -- 使用参数日期作为k1值
    ua.user_id,
    ua.province_id,
    bp.name as province_name,
    ua.city_id,
    bp.area_code as city_name, -- 这里假设使用area_code作为城市名称,实际应根据实际情况调整
    ua.district_id,
    bp.iso_code as district_name, -- 这里假设使用iso_code作为区域名称,实际应根据实际情况调整
    ua.user_address as detail_address, -- 将user_address字段映射为detail_address
    ua.consignee,
    ua.phone_num,
    ua.is_default,
    now() as create_time, -- 使用当前时间作为create_time
    now() as operate_time, -- 使用当前时间作为operate_time
    null as postal_code, -- 暂无此数据,可根据实际情况调整
    concat(bp.name, ' ', bp.area_code, ' ', bp.iso_code, ' ', ua.user_address) as full_address -- 完整地址拼接
from
    (
        select
            id,
            user_id,
            province_id,
            province_id as city_id, -- 暂用province_id代替city_id
            province_id as district_id, -- 暂用province_id代替district_id
            user_address,
            consignee,
            phone_num,
            is_default
        from ods.ods_user_address_full
    ) ua
    left join
    (
        select
            id,
            name,
            area_code,
            iso_code
        from ods.ods_base_province_full
    ) bp
    on ua.province_id = bp.id;

5.建设经验总结:

  • 统一规范先行:在开始建设前,制定统一的命名规范和数据标准

  • 分阶段建设:先搭建核心业务域,后扩展其他业务域

  • 灵活处理数据缺失:面对不完美的数据源,使用合理的替代方案

  • 重视文档和注释:详细记录表结构、字段含义和处理逻辑

  • 持续优化:随着业务发展,不断完善DWD层数据模型

3.2 DIM层建设指南

3.2.1 DIM层的基本概念与作用

基本概念维度层是数据仓库架构中专门用于存储和管理维度数据的层次,位于ODS层之上,为DWD、DWS和ADS层提供标准化的维度信息。
维度数据描述业务实体的属性特征,如商品、用户、时间、地理位置等,它们与事实表(如订单、支付)结合,构成完整的业务分析视图。
核心作用提供统一维度视图
●标准化企业维度定义,解决源系统维度不一致问题数据质量保障
●对维度数据进行清洗、去重和一致性校验提升查询效率
●预先整合维度信息,减少复杂分析时的表连接操作支持历史追溯
●记录维度变化历史,支持不同时间点的历史分析降低冗余度
●避免在各层重复维护维度属性信息
典型表类型DIM层表主要分为两大类:常规维度表
如商品维度表、品牌维度表等,一般采用全量更新模式缓慢变化维度(SCD)
记录随时间变化的维度属性,主要有三种类型:
●SCD Type 1:直接覆盖更新,不保留历史
●SCD Type 2:保留历史版本,通过有效期标记区分
●SCD Type 3:保留有限历史,通过额外字段存储

3.2.2 设计原则与策略

1.表设计原则

用户地址信息是电商系统的重要基础数据,需要支持:

  • 包含维度的所有关键属性,满足分析需求一致性

  • 提供统一标准的维度定义,消除歧义稳定性

  • 维度设计应具备稳定性,避免频繁变更可用性

  • 优化查询效率,支持高并发访问可追溯性

  • 对于关键维度,保留历史变更记录

2.更新策略选择

根据维度变化特性选择合适的更新策略:

  • 适用于变化频率低、数据量小的维度,如商品类目增量更新

  • 适用于持续新增的维度,如新商品拉链表设计

  • 适用于需要保留历史版本的维度,如用户属性变化

3.2.3 案例分析

3.2.3.1 商品维度表实现
-- 以Doris表来构建商品维度表(dim_sku_full)
CREATE TABLE dim.dim_sku_full
(
    `id`                   VARCHAR(255) COMMENT 'SKU ID,商品唯一标识',
    `k1`                   DATE NOT NULL COMMENT '分区字段,数据日期',
    `price`                DECIMAL(16, 2) COMMENT '商品价格,单位元',
    `sku_name`             STRING COMMENT '商品名称,展示用',
    -- 其他商品属性字段
    `category3_id`         STRING COMMENT '三级分类ID,最细粒度的商品分类',
    `category3_name`       STRING COMMENT '三级分类名称,如"休闲男鞋"',
    `category2_id`         STRING COMMENT '二级分类ID,中间层级商品分类',
    `category2_name`       STRING COMMENT '二级分类名称,如"男鞋"',
    `category1_id`         STRING COMMENT '一级分类ID,顶层商品分类',
    `category1_name`       STRING COMMENT '一级分类名称,如"鞋靴"',
    `tm_id`                STRING COMMENT '品牌ID,品牌唯一标识',
    `tm_name`              STRING COMMENT '品牌名称,如"Nike"、"Adidas"',
    -- 扩展属性字段
    `attr_ids`             ARRAY<int(11)> COMMENT '平台属性ID集合',
    `sale_attr_ids`        ARRAY<int(11)> COMMENT '销售属性ID集合',
    `create_time`          STRING COMMENT '创建时间,商品首次录入时间'
)
    ENGINE=OLAP
UNIQUE KEY(`id`,`k1`) -- 使用商品ID和日期作为联合主键
PARTITION BY RANGE(`k1`) () -- 按日期范围分区
DISTRIBUTED BY HASH(`id`) -- 按商品ID哈希分布

设计特点分析

  • 宽表设计:整合了商品所有相关维度信息(SKU基本信息、分类信息、品牌信息)

  • 多级分类:保留一、二、三级分类的ID和名称,便于不同粒度的分析

  • 分区策略:按日期分区,支持历史版本管理和数据生命周期管理

  • 复合主键:通过商品ID和日期组合,确保每天每个商品只有一个状态

3.2.3.2 用户维度拉链表实现

1.表结构设计

对于变化较为频繁且需要历史追溯的维度,如用户信息,我们采用了拉链表设计。

CREATE TABLE dim.dim_user_zip
(
    `id`           VARCHAR(64) COMMENT '用户ID,用户唯一标识',
    `k1`           DATE NOT NULL COMMENT '分区字段,数据日期',
    `login_name`   STRING COMMENT '用户登录名,账号名称',
    `nick_name`    STRING COMMENT '用户昵称,用户自定义展示名',
    `name`         STRING COMMENT '用户真实姓名,已加密',
    -- 其他用户属性字段
    `start_date`   STRING COMMENT '开始日期,当前版本生效开始日期',
    `end_date`     STRING COMMENT '结束日期,当前版本失效日期,9999-12-31表示当前有效版本'
)

2.拉链表处理逻辑

拉链表的核心在于ETL处理逻辑,需要处理两种情况:初始化和增量更新。

2.1 初始化逻辑


-- 用户维度拉链表初始化插入(设置历史起点)
insert into dim.dim_user_zip(id, k1, login_name, ... , start_date, end_date)
select
    id,                       -- 用户ID
    k1,                       -- 分区日期
    login_name,               -- 用户登录名
    -- 其他字段
    md5(name),                -- 用户真实姓名(MD5加密处理)
    md5(phone_num),           -- 手机号码(MD5加密处理)
    md5(email),               -- 电子邮箱(MD5加密处理)
    -- 其他字段
    '2020-06-14' start_date,  -- 拉链起始日期(业务起始日期)
    '9999-12-31' end_date     -- 拉链结束日期(当前有效版本标记)
from ods.ods_user_info_full;

2.2 增量更新逻辑

拉链表的增量更新是其核心价值所在,我们来看实际项目中的逻辑代码:

insert into dim.dim_user_zip(id, k1, login_name, ... , start_date, end_date)
with
    tmp as
    (
        select
            -- 当前拉链表最新有效数据
            old.id old_id,                    -- 原用户ID
            -- 其他old字段
            old.end_date old_end_date,        -- 原版本结束日期

            -- 新增数据(当日变化)
            new.id new_id,                    -- 新用户ID
            -- 其他new字段
            new.end_date new_end_date         -- 新版本结束日期
        from
            -- 查询当前拉链表中最新有效记录
            (
                select * from dim.dim_user_zip
                where end_date = '9999-12-31'  -- 筛选当前有效版本
            ) old
        -- 使用FULL OUTER JOIN合并新旧数据
        full outer join
            -- 当日增量数据处理
            (
                select
                    cast(t1.id as VARCHAR(64)) as id,  -- 转换用户ID类型
                    -- 其他字段处理
                    md5(t1.name) as name,            -- 敏感信息加密
                    -- 其他字段
                    '2024-06-15' as start_date,       -- 新版本开始日期
                    '9999-12-31' as end_date          -- 新版本结束日期
                from
                    (-- 取每个用户最新记录
                        select id, k1, ... , row_number() over (partition by id order by create_time desc) rn
                        from ods.ods_user_info_full
                    ) t1
                where rn=1  -- 只取每个用户的最新记录
            ) new
        on old.id=new.id  -- 按用户ID关联
    )

-- 查询1:处理有变更的用户数据,生成新版本记录
select
    if(new_id is not null, new_id, old_id),         -- 用户ID
    -- 其他字段选择逻辑
    if(new_id is not null, new_start_date, old_start_date), -- 版本开始日期
    if(new_id is not null, new_end_date, old_end_date)      -- 版本结束日期
from tmp
where k2 is not NULL  -- 只处理有新分区数据的记录

union all

-- 查询2:处理原记录的历史版本化,更新结束日期
select
    old_id,                       -- 用户ID
    -- 其他原字段
    old_start_date,               -- 版本开始日期
    cast(date_add(date('${pdate}'), -1) as string) old_end_date  -- 更新版本结束日期为当前日期前一天
from tmp
where k1 is not NULL    -- 有原分区数据
  and old_id is not null  -- 有原用户ID
  and new_id is not null; -- 同时有新用户ID,说明是变更记录

    3.拉链表更新原理解析

    拉链表的核心在于ETL拉链表更新的核心原理在于:保留当前记录

    • 将原有记录的结束日期修改为变更前一天插入新版本

    • 创建新记录,开始日期为变更当天,结束日期为'9999-12-31'新增用户处理

    • 对于新增的用户,直接插入记录,无需处理历史版本使用FULL JOIN

    • 确保既能处理变更用户,也能处理新增用户

    3.3 DWS层建设指南

    3.3.1 设计思路

    3.3.1.1 主题划分

    主题是DWS层最重要的设计要素,需要从以下几个方面考虑:

      1.业务维度划分

      • 交易域:订单、支付、退款等

      • 用户域:注册、登录、画像等

      • 流量域:访问、浏览、跳转等

      • 商品域:商品、类目、品牌等

      2.分析维度考虑

      • 时间维度:日、周、月、年

      • 地理维度:省份、城市、区域

      • 用户维度:设备、渠道、等级

      • 业务维度:品类、品牌、店铺

      3.3.1.2 粒度设计

      1.基础粒度

      • 用户粒度:用户行为分析

      • 商品粒度:商品销售分析

      • 店铺粒度:店铺运营分析

      • 订单粒度:交易过程分析

      2.时间粒度

      • 1d:最近1日汇总,日常监控

      • nd:最近n日汇总,趋势分析

      • td:历史至今汇总,累计分析

      3.3.1.3 指标体系

      1.指标类型

      • 统计指标:数量、金额等

      • 比率指标:占比、转化率等

      • 环比指标:增长率、变化率等

      • 复合指标:加权分数、综合评分等

      2.计算方式

      • 累计值:SUM、COUNT等

      • 去重值:COUNT DISTINCT等

      • 最新值:MAX、LAST_VALUE等

      • 平均值:AVG、MEDIAN等

      3.3.2 实战案例

      1.业务场景

      分析用户购物行为,包括:

      • 商品购买情况

      • 用户消费习惯

      • 品类偏好分析

      • 支付方式分析

      2.表设计方案

      -- 用户商品交易行为汇总表(最近N日)
      CREATE TABLE dws.dws_trade_user_sku_order_nd (
          user_id         STRING COMMENT '用户ID',
          sku_id          STRING COMMENT '商品ID',
          k1              DATE COMMENT '数据日期',
      
          -- 商品维度冗余
          sku_name        STRING COMMENT '商品名称',
          category1_id    STRING COMMENT '一级品类ID',
          category1_name  STRING COMMENT '一级品类名称',
          tm_id           STRING COMMENT '品牌ID',
          tm_name         STRING COMMENT '品牌名称',
      
          -- 7日汇总指标
          order_count_7d  BIGINT COMMENT '7日下单次数',
          order_num_7d    BIGINT COMMENT '7日购买件数',
          order_amount_7d DECIMAL(16,2) COMMENT '7日下单金额',
      
          -- 30日汇总指标
          order_count_30d BIGINT COMMENT '30日下单次数',
          order_num_30d   BIGINT COMMENT '30日购买件数',
          order_amount_30d DECIMAL(16,2) COMMENT '30日下单金额'
      ) COMMENT '交易域用户商品粒度订单最近N日汇总表'
      PARTITION BY k1;

      3.实现方案

      1.维度关联

      -- 1. 关联商品维度信息
      SELECT 
          od.user_id, od.sku_id, od.k1,
      
          -- 2. 冗余维度属性
          COALESCE(sku.sku_name, '未知商品') as sku_name,
          COALESCE(sku.category1_id, '-1') as category1_id,
          COALESCE(sku.category1_name, '未知品类') as category1_name,
          COALESCE(sku.tm_id, '-1') as tm_id,
          COALESCE(sku.tm_name, '未知品牌') as tm_name,
      
          -- 3. 统计指标
          od.order_count_1d,
          od.order_num_1d,
          od.order_amount_1d
      FROM order_detail od
      LEFT JOIN dim.dim_sku_full sku
          ON od.sku_id = sku.id
          AND sku.k1 = (
              SELECT MAX(k1) 
              FROM dim.dim_sku_full 
              WHERE k1 <= DATE('${pdate}')
          );

      2.指标计算

      -- 1. 计算汇总指标
      SELECT
          user_id, sku_id, k1,
          sku_name, category1_id, category1_name,
          tm_id, tm_name,
      
          -- 2. 计算7日累计
          SUM(IF(k1 >= DATE_ADD(DATE('${pdate}'), -6),
                 order_count_1d, 0)) AS order_count_7d,
          SUM(IF(k1 >= DATE_ADD(DATE('${pdate}'), -6),
                 order_num_1d, 0)) AS order_num_7d,
          SUM(IF(k1 >= DATE_ADD(DATE('${pdate}'), -6),
                 order_amount_1d, 0)) AS order_amount_7d,
      
          -- 3. 计算30日累计
          SUM(order_count_1d) AS order_count_30d,
          SUM(order_num_1d) AS order_num_30d,
          SUM(order_amount_1d) AS order_amount_30d
      FROM dws.dws_trade_user_sku_order_1d
      WHERE k1 >= DATE_ADD(DATE('${pdate}'), -29)
      GROUP BY 
          user_id, sku_id, k1,
          sku_name, category1_id, category1_name,
          tm_id, tm_name;

      3.4 ADS层建设指南

      3.4.1 建设思路

      1.设计原则

      • 业务导向:ADS层设计应以业务需求为核心,确保数据能够直接支持业务决策和运营分析。

      • 性能优化:针对高频查询场景进行优化,包括合理的分区策略、索引设计和物化视图。

      • 数据一致性:确保数据口径统一,避免不同报表之间的数据不一致。

      • 可扩展性:设计时考虑未来业务扩展需求,预留足够的扩展空间。

      • 易用性:提供简单直观的数据结构,降低业务人员使用门槛。

      2.数据模型设计

      ADS层通常采用星型模型或雪花模型,主要包含以下几类表:

      • 汇总事实表:按不同维度(时间、地区、产品等)汇总的事实数据。

      • 维度表:包含业务实体的属性信息,如客户、产品、地区等。

      • 指标表:存储预计算的业务指标,如转化率、留存率等。

      • 报表表:直接面向报表展示的宽表,包含多个维度的指标。

      3.数据更新策略

      • 增量更新:对于大表,采用增量更新策略,只处理新增或变更的数据。

      • 全量刷新:对于小表或需要保证数据一致性的场景,采用全量刷新策略。

      • 定时调度:根据业务需求设置合理的调度周期,如每日、每周或每月。

        3.4.2 实战案例

        1.业务背景

        电商平台需要对用户进行价值分层,以便进行精准营销和个性化服务。基于RFM模型(Recency、Frequency、Monetary)对用户进行价值评估,并计算用户的生命周期价值,为运营决策提供数据支持。

        2.数据来源

        • DWS层:用户交易宽表(dws_trade_user_order_td)和用户登录宽表(dws_user_user_login_td)

        • ADS层:历史用户价值分析数据(用于计算价值发展趋势)

        3.实现方案

        3.1 建表(ads.ads_user_value_analysis)

        -- 用户价值分析表
        CREATE TABLE IF NOT EXISTS ads.ads_user_value_analysis
        (
            dt                      DATE COMMENT '统计日期',
            user_id                 BIGINT COMMENT '用户ID',
            order_count_td          BIGINT COMMENT '累计下单次数',
            order_amount_td         DECIMAL(20,2) COMMENT '累计下单金额',
            order_last_date         DATE COMMENT '最近下单日期',
            order_first_date        DATE COMMENT '首次下单日期',
            login_count_td          BIGINT COMMENT '累计登录次数',
            login_last_date         DATE COMMENT '最近登录日期',
            average_order_amount    DECIMAL(20,2) COMMENT '平均客单价',
            purchase_cycle_days     INT COMMENT '平均购买周期(天)',
            account_days            INT COMMENT '账号存续天数',
            life_time_value         DECIMAL(20,2) COMMENT '生命周期价值(LTV)',
            recency_score           INT COMMENT '最近活跃度评分(R)',
            frequency_score         INT COMMENT '活动频次评分(F)',
            monetary_score          INT COMMENT '消费金额评分(M)',
            rfm_score               INT COMMENT 'RFM总分',
            user_value_level        STRING COMMENT '用户价值分层',
            active_status           STRING COMMENT '活跃状态',
            life_cycle_status       STRING COMMENT '生命周期状态',
            shopping_preference     STRING COMMENT '购物偏好',
            growth_trend            STRING COMMENT '价值发展趋势'
        )
        COMMENT '用户价值分层分析报表,基于RFM模型计算用户价值'
        PARTITIONED BY (dt STRING)
        STORED AS ORC
        TBLPROPERTIES ('orc.compress'='SNAPPY');

        3.2 核心指标计算

        RFM模型评分

        • Recency(最近购买时间):根据最近一次购买距今的天数评分(1-5分)

        • Frequency(购买频率):根据累计购买次数评分(1-5分)

        • Monetary(消费金额):根据累计消费金额评分(1-5分)

        用户价值分层

        • 高价值:RFM总分≥13分

        • 中高价值:RFM总分10-12分

        • 中价值:RFM总分7-9分

        • 低价值:RFM总分4-6分

        • 流失风险:RFM总分≤3分

        生命周期价值(LTV)

        • 计算公式:平均客单价 × 年购买频率 × 预期客户生命周期(年)

        • 年购买频率:总购买次数×365/账号存续天数

        用户状态分类

        • 活跃状态:基于最近交易和登录时间

        • 生命周期状态:基于交易行为和订单历史

        • 购物偏好:基于购买频率和金额

        • 价值发展趋势:比较当前RFM评分与30天前的评分

        3.3 具体逻辑实现

        INSERT INTO ads.ads_user_value_analysis
        (dt, user_id, order_count_td, order_amount_td, order_last_date, order_first_date, 
        login_count_td, login_last_date, average_order_amount, purchase_cycle_days, account_days, 
        life_time_value, recency_score, frequency_score, monetary_score, rfm_score, 
        user_value_level, active_status, life_cycle_status, shopping_preference, growth_trend)
        SELECT
            -- 基础日期统计
            date('${pdate}') AS dt,       -- 统计日期,使用调度日期参数
            t1.user_id AS user_id,        -- 用户ID 
            t1.order_count_td,            -- 累计下单次数,来自交易宽表
            t1.total_amount_td,           -- 累计下单金额,来自交易宽表
            -- 格式化日期为yyyy-MM-dd格式
            date_format(t1.order_last_date, '%Y-%m-%d') AS order_last_date,  -- 最近下单日期
            date_format(t1.order_first_date, '%Y-%m-%d') AS order_first_date, -- 首次下单日期
            t2.login_count_td,            -- 累计登录次数,来自用户登录宽表
            date_format(t2.login_last_date, '%Y-%m-%d') AS login_last_date,  -- 最近登录日期
        
            -- 计算衍生指标
            -- 计算平均客单价 = 总金额/订单数
            CASE WHEN t1.order_count_td > 0 THEN t1.total_amount_td/t1.order_count_td ELSE 0 END AS average_order_amount,
            -- 计算平均购买周期(天) = (最后订单日期-首次订单日期)/(订单数-1)
            CASE WHEN t1.order_count_td > 1 
                 THEN datediff(t1.order_last_date, t1.order_first_date)/(t1.order_count_td-1) 
                 ELSE NULL END AS purchase_cycle_days,
            -- 计算账号存续天数 = 当前日期-注册日期
            datediff(current_date(), t2.register_date) AS account_days,
        
            -- 计算生命周期价值(LTV) = 平均客单价 * 年购买频率 * 预期客户生命周期(年)
            -- 年购买频率计算方式: 总购买次数*365/账号存续天数,即年化购买频率
            -- 预期客户生命周期取3年作为默认预估
            CASE WHEN t1.order_count_td > 0 AND datediff(current_date(), t2.register_date) > 0 
                 THEN (t1.total_amount_td/t1.order_count_td) * (t1.order_count_td*365/datediff(current_date(), t2.register_date)) * 3 
                 ELSE 0 END AS life_time_value,
        
            -- RFM模型计算 - 为每个维度打分(1-5分)
            -- Recency(最近购买时间)评分: 越近分数越高
            CASE 
                WHEN datediff(current_date(), t1.order_last_date) <= 30 THEN 5  -- 30天内
                WHEN datediff(current_date(), t1.order_last_date) <= 60 THEN 4  -- 31-60天
                WHEN datediff(current_date(), t1.order_last_date) <= 90 THEN 3  -- 61-90天
                WHEN datediff(current_date(), t1.order_last_date) <= 180 THEN 2 -- 91-180天
                ELSE 1                                                          -- 180天以上
            END AS recency_score,
        
            -- Frequency(购买频率)评分: 购买次数越多分数越高
            CASE
                WHEN t1.order_count_td >= 20 THEN 5  -- 20次及以上
                WHEN t1.order_count_td >= 10 THEN 4  -- 10-19次
                WHEN t1.order_count_td >= 5 THEN 3   -- 5-9次
                WHEN t1.order_count_td >= 2 THEN 2   -- 2-4次
                ELSE 1                               -- 1次
            END AS frequency_score,
        
            -- Monetary(消费金额)评分: 总消费金额越高分数越高
            CASE
                WHEN t1.total_amount_td >= 10000 THEN 5  -- 1万元及以上
                WHEN t1.total_amount_td >= 5000 THEN 4   -- 5千-1万元
                WHEN t1.total_amount_td >= 2000 THEN 3   -- 2千-5千元
                WHEN t1.total_amount_td >= 500 THEN 2    -- 500-2千元
                ELSE 1                                   -- 500元以下
            END AS monetary_score,
        
            -- 计算RFM总分 = R分 + F分 + M分
            (CASE 
                WHEN datediff(current_date(), t1.order_last_date) <= 30 THEN 5
                WHEN datediff(current_date(), t1.order_last_date) <= 60 THEN 4
                WHEN datediff(current_date(), t1.order_last_date) <= 90 THEN 3
                WHEN datediff(current_date(), t1.order_last_date) <= 180 THEN 2
                ELSE 1
            END) + 
            (CASE
                WHEN t1.order_count_td >= 20 THEN 5
                WHEN t1.order_count_td >= 10 THEN 4
                WHEN t1.order_count_td >= 5 THEN 3
                WHEN t1.order_count_td >= 2 THEN 2
                ELSE 1
            END) + 
            (CASE
                WHEN t1.total_amount_td >= 10000 THEN 5
                WHEN t1.total_amount_td >= 5000 THEN 4
                WHEN t1.total_amount_td >= 2000 THEN 3
                WHEN t1.total_amount_td >= 500 THEN 2
                ELSE 1
            END) AS rfm_score,
        
            -- 用户价值分层: 根据RFM总分(3-15分)进行分层
            CASE
                WHEN (CASE 
                    WHEN datediff(current_date(), t1.order_last_date) <= 30 THEN 5
                    WHEN datediff(current_date(), t1.order_last_date) <= 60 THEN 4
                    WHEN datediff(current_date(), t1.order_last_date) <= 90 THEN 3
                    WHEN datediff(current_date(), t1.order_last_date) <= 180 THEN 2
                    ELSE 1
                END + 
                CASE
                    WHEN t1.order_count_td >= 20 THEN 5
                    WHEN t1.order_count_td >= 10 THEN 4
                    WHEN t1.order_count_td >= 5 THEN 3
                    WHEN t1.order_count_td >= 2 THEN 2
                    ELSE 1
                END + 
                CASE
                    WHEN t1.total_amount_td >= 10000 THEN 5
                    WHEN t1.total_amount_td >= 5000 THEN 4
                    WHEN t1.total_amount_td >= 2000 THEN 3
                    WHEN t1.total_amount_td >= 500 THEN 2
                    ELSE 1
                END) >= 13 THEN '高价值'      -- 13-15分
                WHEN (CASE 
                    WHEN datediff(current_date(), t1.order_last_date) <= 30 THEN 5
                    WHEN datediff(current_date(), t1.order_last_date) <= 60 THEN 4
                    WHEN datediff(current_date(), t1.order_last_date) <= 90 THEN 3
                    WHEN datediff(current_date(), t1.order_last_date) <= 180 THEN 2
                    ELSE 1
                END + 
                CASE
                    WHEN t1.order_count_td >= 20 THEN 5
                    WHEN t1.order_count_td >= 10 THEN 4
                    WHEN t1.order_count_td >= 5 THEN 3
                    WHEN t1.order_count_td >= 2 THEN 2
                    ELSE 1
                END + 
                CASE
                    WHEN t1.total_amount_td >= 10000 THEN 5
                    WHEN t1.total_amount_td >= 5000 THEN 4
                    WHEN t1.total_amount_td >= 2000 THEN 3
                    WHEN t1.total_amount_td >= 500 THEN 2
                    ELSE 1
                END) >= 10 THEN '中高价值'    -- 10-12分
                WHEN (CASE 
                    WHEN datediff(current_date(), t1.order_last_date) <= 30 THEN 5
                    WHEN datediff(current_date(), t1.order_last_date) <= 60 THEN 4
                    WHEN datediff(current_date(), t1.order_last_date) <= 90 THEN 3
                    WHEN datediff(current_date(), t1.order_last_date) <= 180 THEN 2
                    ELSE 1
                END + 
                CASE
                    WHEN t1.order_count_td >= 20 THEN 5
                    WHEN t1.order_count_td >= 10 THEN 4
                    WHEN t1.order_count_td >= 5 THEN 3
                    WHEN t1.order_count_td >= 2 THEN 2
                    ELSE 1
                END + 
                CASE
                    WHEN t1.total_amount_td >= 10000 THEN 5
                    WHEN t1.total_amount_td >= 5000 THEN 4
                    WHEN t1.total_amount_td >= 2000 THEN 3
                    WHEN t1.total_amount_td >= 500 THEN 2
                    ELSE 1
                END) >= 7 THEN '中价值'       -- 7-9分
                WHEN (CASE 
                    WHEN datediff(current_date(), t1.order_last_date) <= 30 THEN 5
                    WHEN datediff(current_date(), t1.order_last_date) <= 60 THEN 4
                    WHEN datediff(current_date(), t1.order_last_date) <= 90 THEN 3
                    WHEN datediff(current_date(), t1.order_last_date) <= 180 THEN 2
                    ELSE 1
                END + 
                CASE
                    WHEN t1.order_count_td >= 20 THEN 5
                    WHEN t1.order_count_td >= 10 THEN 4
                    WHEN t1.order_count_td >= 5 THEN 3
                    WHEN t1.order_count_td >= 2 THEN 2
                    ELSE 1
                END + 
                CASE
                    WHEN t1.total_amount_td >= 10000 THEN 5
                    WHEN t1.total_amount_td >= 5000 THEN 4
                    WHEN t1.total_amount_td >= 2000 THEN 3
                    WHEN t1.total_amount_td >= 500 THEN 2
                    ELSE 1
                END) >= 4 THEN '低价值'       -- 4-6分
                ELSE '流失风险'                                                                 -- 3分
            END AS user_value_level,
        
            -- 活跃状态: 基于最近交易和登录时间
            CASE
                WHEN datediff(current_date(), t1.order_last_date) <= 30 OR datediff(current_date(), t2.login_last_date) <= 7 THEN '活跃'   -- 30天内有交易或7天内有登录
                WHEN datediff(current_date(), t1.order_last_date) <= 90 OR datediff(current_date(), t2.login_last_date) <= 30 THEN '沉默'  -- 90天内有交易或30天内有登录
                ELSE '流失'                                                                                                               -- 超过90天未交易且超过30天未登录
            END AS active_status,
        
            -- 生命周期状态: 基于交易行为和订单历史
            CASE
                WHEN datediff(current_date(), t1.order_first_date) <= 30 AND t1.order_count_td <= 2 THEN '新用户'     -- 30天内首次购买且购买次数<=2次
                WHEN t1.order_count_td >= 3 AND (CASE 
                    WHEN datediff(current_date(), t1.order_last_date) <= 30 THEN 5
                    WHEN datediff(current_date(), t1.order_last_date) <= 60 THEN 4
                    WHEN datediff(current_date(), t1.order_last_date) <= 90 THEN 3
                    WHEN datediff(current_date(), t1.order_last_date) <= 180 THEN 2
                    ELSE 1
                END) >= 4 THEN '成长期'                                      -- 购买>=3次且近期活跃(60天内)
                WHEN t1.order_count_td >= 5 AND (CASE 
                    WHEN datediff(current_date(), t1.order_last_date) <= 30 THEN 5
                    WHEN datediff(current_date(), t1.order_last_date) <= 60 THEN 4
                    WHEN datediff(current_date(), t1.order_last_date) <= 90 THEN 3
                    WHEN datediff(current_date(), t1.order_last_date) <= 180 THEN 2
                    ELSE 1
                END) >= 3 THEN '成熟期'                                      -- 购买>=5次且90天内有购买
                WHEN (CASE 
                    WHEN datediff(current_date(), t1.order_last_date) <= 30 THEN 5
                    WHEN datediff(current_date(), t1.order_last_date) <= 60 THEN 4
                    WHEN datediff(current_date(), t1.order_last_date) <= 90 THEN 3
                    WHEN datediff(current_date(), t1.order_last_date) <= 180 THEN 2
                    ELSE 1
                END) <= 2 AND (CASE
                    WHEN t1.order_count_td >= 20 THEN 5
                    WHEN t1.order_count_td >= 10 THEN 4
                    WHEN t1.order_count_td >= 5 THEN 3
                    WHEN t1.order_count_td >= 2 THEN 2
                    ELSE 1
                END) >= 3 THEN '衰退期'                                        -- 购买次数>=5但超过90天未购买
                WHEN (CASE 
                    WHEN datediff(current_date(), t1.order_last_date) <= 30 THEN 5
                    WHEN datediff(current_date(), t1.order_last_date) <= 60 THEN 4
                    WHEN datediff(current_date(), t1.order_last_date) <= 90 THEN 3
                    WHEN datediff(current_date(), t1.order_last_date) <= 180 THEN 2
                    ELSE 1
                END) >= 3 AND datediff(t1.order_last_date, t0.lag_order_date) > 90 THEN '回流'          -- 最近90天内有购买但之前超过90天未购买
                ELSE '新用户'                                                                                         -- 默认为新用户
            END AS life_cycle_status,
        
            -- 购物偏好: 基于购买频率和金额
            CASE
                WHEN (CASE
                    WHEN t1.order_count_td >= 20 THEN 5
                    WHEN t1.order_count_td >= 10 THEN 4
                    WHEN t1.order_count_td >= 5 THEN 3
                    WHEN t1.order_count_td >= 2 THEN 2
                    ELSE 1
                END) >= 4 AND (CASE
                    WHEN t1.total_amount_td >= 10000 THEN 5
                    WHEN t1.total_amount_td >= 5000 THEN 4
                    WHEN t1.total_amount_td >= 2000 THEN 3
                    WHEN t1.total_amount_td >= 500 THEN 2
                    ELSE 1
                END) <= 3 THEN '高频低额'    -- 高频率低金额: 购买频繁但单价较低
                WHEN (CASE
                    WHEN t1.order_count_td >= 20 THEN 5
                    WHEN t1.order_count_td >= 10 THEN 4
                    WHEN t1.order_count_td >= 5 THEN 3
                    WHEN t1.order_count_td >= 2 THEN 2
                    ELSE 1
                END) <= 3 AND (CASE
                    WHEN t1.total_amount_td >= 10000 THEN 5
                    WHEN t1.total_amount_td >= 5000 THEN 4
                    WHEN t1.total_amount_td >= 2000 THEN 3
                    WHEN t1.total_amount_td >= 500 THEN 2
                    ELSE 1
                END) >= 4 THEN '低频高额'    -- 低频率高金额: 购买较少但大额消费 
                WHEN (CASE
                    WHEN t1.order_count_td >= 20 THEN 5
                    WHEN t1.order_count_td >= 10 THEN 4
                    WHEN t1.order_count_td >= 5 THEN 3
                    WHEN t1.order_count_td >= 2 THEN 2
                    ELSE 1
                END) >= 4 AND (CASE
                    WHEN t1.total_amount_td >= 10000 THEN 5
                    WHEN t1.total_amount_td >= 5000 THEN 4
                    WHEN t1.total_amount_td >= 2000 THEN 3
                    WHEN t1.total_amount_td >= 500 THEN 2
                    ELSE 1
                END) >= 4 THEN '高频高额'    -- 高频率高金额: 高价值客户,频繁且大额
                ELSE '低频低额'                                                     -- 低频率低金额: 低价值客户
            END AS shopping_preference,
        
            -- 价值发展趋势: 比较当前RFM评分与30天前的评分
            CASE
                WHEN (CASE 
                    WHEN datediff(current_date(), t1.order_last_date) <= 30 THEN 5
                    WHEN datediff(current_date(), t1.order_last_date) <= 60 THEN 4
                    WHEN datediff(current_date(), t1.order_last_date) <= 90 THEN 3
                    WHEN datediff(current_date(), t1.order_last_date) <= 180 THEN 2
                    ELSE 1
                END + 
                CASE
                    WHEN t1.order_count_td >= 20 THEN 5
                    WHEN t1.order_count_td >= 10 THEN 4
                    WHEN t1.order_count_td >= 5 THEN 3
                    WHEN t1.order_count_td >= 2 THEN 2
                    ELSE 1
                END + 
                CASE
                    WHEN t1.total_amount_td >= 10000 THEN 5
                    WHEN t1.total_amount_td >= 5000 THEN 4
                    WHEN t1.total_amount_td >= 2000 THEN 3
                    WHEN t1.total_amount_td >= 500 THEN 2
                    ELSE 1
                END) > COALESCE(t3.previous_rfm_score, 0) THEN '上升'    -- 当前分数高于30天前,趋势上升
                WHEN (CASE 
                    WHEN datediff(current_date(), t1.order_last_date) <= 30 THEN 5
                    WHEN datediff(current_date(), t1.order_last_date) <= 60 THEN 4
                    WHEN datediff(current_date(), t1.order_last_date) <= 90 THEN 3
                    WHEN datediff(current_date(), t1.order_last_date) <= 180 THEN 2
                    ELSE 1
                END + 
                CASE
                    WHEN t1.order_count_td >= 20 THEN 5
                    WHEN t1.order_count_td >= 10 THEN 4
                    WHEN t1.order_count_td >= 5 THEN 3
                    WHEN t1.order_count_td >= 2 THEN 2
                    ELSE 1
                END + 
                CASE
                    WHEN t1.total_amount_td >= 10000 THEN 5
                    WHEN t1.total_amount_td >= 5000 THEN 4
                    WHEN t1.total_amount_td >= 2000 THEN 3
                    WHEN t1.total_amount_td >= 500 THEN 2
                    ELSE 1
                END) < COALESCE(t3.previous_rfm_score, 0) THEN '下降'    -- 当前分数低于30天前,趋势下降
                ELSE '稳定'                                                      -- 分数相等,趋势稳定
            END AS growth_trend
        FROM
        (
            -- 订单数据: 获取用户交易相关信息
            SELECT 
                user_id,
                k1,
                order_date_last,
                LAG(order_date_last, 1, NULL) OVER(PARTITION BY user_id ORDER BY k1) AS lag_order_date  -- 获取上一次最近下单日期,用于计算回流状态
            FROM dws.dws_trade_user_order_td
            WHERE k1 = date('${pdate}')  -- 取当天分区数据
        ) t0
        JOIN
        (
            -- 订单数据: 获取用户交易相关信息
            SELECT 
                user_id,
                SUM(order_count_td) AS order_count_td,           -- 累计下单次数
                SUM(total_amount_td) AS total_amount_td,         -- 累计下单金额
                MAX(order_date_last) AS order_last_date,         -- 最近下单日期
                MIN(order_date_first) AS order_first_date        -- 首次下单日期
            FROM dws.dws_trade_user_order_td
            WHERE k1 = date('${pdate}')  -- 取当天分区数据
            GROUP BY user_id
        ) t1
        ON t0.user_id = t1.user_id
        JOIN
        (
            -- 登录数据: 获取用户登录相关信息
            SELECT 
                user_id,
                SUM(login_count_td) AS login_count_td,         -- 累计登录次数
                MAX(login_date_last) AS login_last_date,       -- 最近登录日期
                date('2020-01-01') AS register_date            -- 注册日期,使用默认值
            FROM dws.dws_user_user_login_td
            WHERE k1 = date('${pdate}')  -- 取当天分区数据
            GROUP BY user_id
        ) t2
        ON t1.user_id = t2.user_id
        LEFT JOIN
        (
            -- 上月RFM评分数据: 用于计算价值发展趋势
            SELECT 
                user_id,
                recency_score + frequency_score + monetary_score AS previous_rfm_score  -- 30天前的RFM总分
            FROM ads.ads_user_value_analysis
            WHERE dt = date_sub(date('${pdate}'), 30)  -- 取30天前的数据
        ) t3
        ON t1.user_id = t3.user_id;

        总结:ADS(Application Data Store)层是数据仓库的最顶层,直接面向业务应用。其建设核心是以业务需求为导向,将经过清洗、转换和汇总的数据以业务友好的方式呈现。ADS层设计应遵循业务导向、性能优化、数据一致性、可扩展性和易用性五大原则。主要包含汇总事实表、维度表、指标表和报表表等数据模型。数据更新策略包括增量更新和全量刷新,需根据业务场景选择。ADS层实现需要高性能存储引擎(如Doris)和计算框架,并建立完善的数据质量控制机制。通过ADS层,企业可以实现精准营销、会员运营、风险控制、客户服务和产品优化等业务目标,为决策提供数据支持。

        四、决策建议

        通过以下问题判断是否需要分层:

        1. 数据规模:是否超过单机处理能力?

        2. 团队规模:是否有专职数据工程师负责模型设计?

        3. 业务复杂度:是否需要支持多个部门或业务线的多样化需求?

        4. 长期规划:是否预期未来3年数据量或业务复杂度会显著增长?

        结论

        • 短期/简单场景:可不分层,但需预留扩展性。

        • 中长期/复杂场景:分层是必选项,可从小规模开始逐步迭代。

        最终,是否分层是一个权衡ROI(投资回报率)的问题。建议从最小化可行方案出发,随着业务发展逐步演进架构。

        4.1 不同情况建设建议

        不同情况特点说明
        时间紧任务重若时间紧任务重,一周就要看结果,那就别说了,直接连业务数据库是最合适的。要啥分层?没那个时间。
        业务简单+数据源少且简单如果说公司业务简单,且相对比较固定,数据来源不多,结构也很清晰,需求也不多,可以ODS+DWD+DWS,三层足矣。
        ODS起到解耦业务数据库+异构数据源的问题,DWD解决数据脏乱差的问题,DWS直接面向前台业务需求。够了。
        业务一般复杂+公司战略变化不大如果说公司业务一般复杂,每年都要跟着战略变,那就中规中矩的设计4层,多一层DWB层做汇总,多一层解耦,业务变化的时候,我们只改DWS层就好了,最多穿透到DWB层。每年按照战略调整一次,工作量也不会太大,最重要的是能保证底层结构的稳定和数据分析的可持续性。
        业务多非常复杂如果说公司业务非常复杂,业务线众多,那就在4层基础上加一层DM,每条业务线一个单独的DM。如果是集团型的,DM还可以设置在汇总层下面。放在那里,取决于组织结构。
        业务变化频繁如果说公司业务变化非常频繁,仨月变一次业务方向,后台数据每天都改数据库,说实话,没啥好办法。要么用人力堆,要么提取相对比较固定的内容去建设数仓,变化太快的直接做固定报表吧。前后都变,中间的没法干活,怎么解耦都没用。谁有好办法,可以告诉我,我去学习一下。
        其他注意:
        ●至于DIM层,不管哪一种方式,都需要。
        ●互联网模式加一层标签层。
        ●大数据环境可以用宽表层替换DWS层(其实都一样)。

        五、数仓分层示例参考

        5.1 三层数仓架构(方案一)

         3层数据仓库建设的架构一般指的是ODS、DW(数仓)、DM(数据集市),其中DW和DM又可以再拆成N层。不过我没有找到大厂的这种架构分享,大抵是因为这么各大厂不屑于画这么简单的分层架构图吧。

         5.2 三层数仓架构(方案二)

         另外一种3层数仓是ODS+DWD+DWS,这样就能满足解耦业务数据库、数据标准化+统一化和面向应用等基础功能。同样也没有找到大厂的3层分享案例。

        5.3 美团大交通4层实时数仓架构

         特意放上实时数仓的架构图,就是想说明一下无论是实时数仓还是离线数仓,架构都是一样的,该分几层分几层。只不过实时数仓用的是Kafka等MQ作为实时存储介质。

         5.4 搜狐5层数据仓库架构

         这是搜狐的5层数据仓库架构。之所以放搜狐的案例,是因为这里有一个STG层。这边把ODS细分为STG和ODS。STG是数据缓冲层,相当于贴源层,就是跟业务系统保持一致的结构,而这个架构中的ODS是经过简单清洗的明细数据。

        5.5 美团酒旅6层数仓架构

        这是美团酒旅业务的数仓架构,业务足够复杂,所以分成6层了。以第3代为例,ODS、数据整合层、多维明细层、汇总层、主题层、应用层。每一层的目的非常清晰。

        • ODS:汇聚原始数据;

        • 数据整合层:对数据进行清洗、筛选、整合等操作;

        • 多维明细层:进行维度建模;

        • 汇总层:进行各级汇总;

        • 主题层:按照业务领域,切分主题域,提供面向业务主题的数据集;

        • 应用层:面向前端应用汇聚数据。

        另外,美团的这张图,也体现了架构的另外一个重点:架构是需要不断的优化调整的,不能超前太多,也不能脱离业务。按照Inmon和Kimball吵了十几年的经验上看,建议架构设计时,按超越当前实际情况1~1.5年的设计是比较合适的。超越太长会导致建设期过长或者条件不成熟而失败,太短则修改太过频繁。

        六、总结

        数据仓库分层的核心逻辑是解耦。需要在有限时间、资源等条件下满足业务需求,同时又要兼顾业务的快速变化。所以我们作为数据架构师,需要兼顾业务的复杂变化,以及开发的复杂度和可维护性,在两者之间做一个平衡和取舍。

        至于分几层,建议按照目前的业务和建设现状,进行合理解构和分层设计,一般刚开始做,建议3、4层。规划1-1.5年的架构,然后不断的建设、优化、再优化。不断逼近满足所有需求。

          评论
          添加红包

          请填写红包祝福语或标题

          红包个数最小为10个

          红包金额最低5元

          当前余额3.43前往充值 >
          需支付:10.00
          成就一亿技术人!
          领取后你会自动成为博主和红包主的粉丝 规则
          hope_wisdom
          发出的红包
          实付
          使用余额支付
          点击重新获取
          扫码支付
          钱包余额 0

          抵扣说明:

          1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
          2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

          余额充值