第一章 - 数据仓库是什么

1.1 从业务痛点理解数仓价值

现实场景:一个零售公司的数据分析困境

背景: 某中型零售公司"优品生活",拥有线上商城、线下门店、微信小程序三个销售渠道,使用不同的业务系统。

四个典型数据困境:

困境一:报表系统为什么越来越慢?
sql 复制代码
-- 业务人员想分析"昨日各门店销售额",需要查询生产数据库
SELECT store_name, SUM(order_amount) 
FROM orders 
JOIN stores ON orders.store_id = stores.id
WHERE DATE(order_time) = '2024-01-15'  -- 昨日
GROUP BY store_name;

问题:

  • 订单表有5000万条记录,每次查询全表扫描
  • 与门店表关联时影响线上订单处理
  • 财务、市场、运营都在同一时间跑报表,数据库CPU飙升至90%

根本原因: OLTP(在线事务处理)系统不是为分析查询设计的

困境二:部门数据为什么对不上?

每月销售会议上的尴尬对话:

  • 财务总监: "1月线上销售额是2850万"
  • 市场总监: "不对,我们统计是3120万"
  • 运营总监: "我们系统显示2990万"

数据不一致的原因:

复制代码
三个系统,三种计算逻辑:
1. 财务系统:已收款订单(剔除退款)
2. 市场系统:所有下单金额(包括未支付)
3. 运营系统:已发货订单(不包括取消)

三个系统,三种数据定义:
1. "北京朝阳门店"在财务系统编码:BJ-CY-001
2. 在市场系统:STORE_110105
3. 在运营系统:北京1号店
困境三:数据分析为什么要等一天?

业务需求: "老板要看今天上午的销售实时情况"

技术现实:

复制代码
08:00 - 业务系统产生新订单
09:00 - 第一次数据同步(凌晨同步还在进行)
12:00 - 凌晨同步完成,开始上午同步
14:00 - 数据清洗转换
16:00 - 数据加载到报表系统
17:00 - 业务人员终于看到数据

等待链条:

复制代码
订单产生 → 业务系统 → 隔夜同步 → ETL处理 → 报表更新
(实时)       (实时)      (T+1)      (2小时)    (1小时)
困境四:为什么需要历史数据分析?

业务场景: "分析去年春节和今年春节的销售对比"

生产数据库的局限性:

sql 复制代码
-- 生产数据库为了性能,通常会:
-- 1. 删除历史数据(只保留3个月)
-- 2. 修改用户信息(覆盖旧地址)
-- 3. 更新商品价格(没有历史记录)

-- 无法回答的问题:
-- "这个客户三年前的购买偏好是什么?"
-- "这个商品价格是如何变化的?"
-- "促销活动的长期效果如何?"

数据仓库的解决方案

针对上述四个困境,数仓提供了系统化的解决方案:

业务痛点 数仓解决方案 技术实现
报表慢 分析查询与事务处理分离 独立的OLAP系统 + 列式存储
数据不一致 建立企业单一事实源 ETL统一清洗 + 标准维度
分析滞后 建立合理的数据更新频率 增量同步 + 分层处理
缺乏历史 保存完整的时态数据 缓慢变化维 + 历史表

1.2 数仓的四大核心特性

特性一:面向主题(Subject-Oriented)

传统系统 vs 数仓的数据组织方式

传统业务系统的数据组织(按功能划分):

复制代码
┌─────────────────────────────────────┐
│          ERP系统(按模块)           │
├─────────┬─────────┬─────────┬───────┤
│ 采购模块 │ 库存模块 │ 销售模块 │ 财务模块│
│ (采购单)  │ (库存量)  │ (销售单)  │ (总账)  │
└─────────┴─────────┴─────────┴───────┘
问题:分析"产品盈利能力"需要跨4个模块查询

数据仓库的数据组织(按主题划分):

复制代码
┌─────────────────────────────────────┐
│       数据仓库(按业务主题)          │
├──────────────┬────────────┬─────────┤
│   "销售"主题  │  "客户"主题 │"产品"主题│
├──────────────┼────────────┼─────────┤
│ • 订单事实    │ • 客户维度  │ • 产品维度│
│ • 退货事实    │ • 地址维度  │ • 分类维度│
│ • 促销事实    │ • 分层维度  │ • 供应商维│
└──────────────┴────────────┴─────────┘
优势:分析"产品盈利能力"在一个主题内完成
主题域划分的实战示例
sql 复制代码
-- 零售公司的典型主题域
CREATE SCHEMA sales;      -- 销售主题:订单、退货、促销
CREATE SCHEMA customer;   -- 客户主题:画像、行为、价值
CREATE SCHEMA product;    -- 产品主题:分类、库存、成本
CREATE SCHEMA finance;    -- 财务主题:收入、成本、利润
CREATE SCHEMA supply_chain; -- 供应链主题:采购、物流、库存

-- 每个主题包含:
-- 1. 核心事实表(发生了什么)
-- 2. 相关维度表(在什么背景下发生的)
-- 3. 聚合汇总表(不同粒度的汇总)

特性二:集成性(Integrated)

数据集成:从"方言"到"普通话"

集成前的混乱状态:

sql 复制代码
-- 系统A(电商平台):用户状态
SELECT user_status FROM users;  -- 返回:'ACTIVE', 'INACTIVE'

-- 系统B(CRM系统):客户状态  
SELECT customer_state FROM customers;  -- 返回:1, 2, 0

-- 系统C(客服系统):用户活跃度
SELECT active_flag FROM clients;  -- 返回:'Y', 'N'

-- 三个系统,同一概念,三种表示!

数据仓库的统一集成:

sql 复制代码
-- ETL转换过程示例
INSERT INTO dim_customer (customer_key, customer_status)
SELECT 
    -- 生成统一代理键
    MD5(COALESCE(user_id::TEXT, customer_id::TEXT)) AS customer_key,
    
    -- 统一状态编码转换
    CASE 
        WHEN user_status = 'ACTIVE' THEN '活跃'
        WHEN customer_state = 1 THEN '活跃'
        WHEN active_flag = 'Y' THEN '活跃'
        WHEN user_status = 'INACTIVE' THEN '不活跃'
        WHEN customer_state IN (0, 2) THEN '不活跃'
        WHEN active_flag = 'N' THEN '不活跃'
        ELSE '未知'
    END AS customer_status
    
FROM 
    -- 跨系统数据源
    source_system_a.users
    FULL OUTER JOIN source_system_b.customers ON ...
    FULL OUTER JOIN source_system_c.clients ON ...
集成性的四个维度
集成维度 内容 示例
命名标准化 统一字段、表名规范 user_id → customer_id
编码统一化 统一代码值含义 1=活跃,0=不活跃
格式规范化 统一数据类型格式 手机号:13800138000
业务规则一致 统一计算逻辑 销售额=实收金额-退款

特性三:非易失性(Non-Volatile)

为什么数据仓库不可修改?

业务数据库 vs 数据仓库的写入模式对比:

sql 复制代码
-- OLTP系统(可修改)
UPDATE orders SET status = 'CANCELLED' WHERE order_id = 1001;
DELETE FROM cart_items WHERE user_id = 5002;
-- 特点:支持增、删、改,数据反映当前状态

-- OLAP系统(不可修改)
INSERT INTO fact_orders VALUES 
    (1001, '2024-01-15', 'COMPLETED', 299.00),
    (1001, '2024-01-16', 'CANCELLED', 299.00);  -- 新增一条取消记录
-- 特点:只支持插入,保留所有历史状态
时态数据存储的三种模式
sql 复制代码
-- 模式1:快照表(每日全量)
CREATE TABLE customer_snapshot (
    snapshot_date DATE,
    customer_id INT,
    customer_name VARCHAR(100),
    customer_status VARCHAR(20),
    total_orders INT,
    total_amount DECIMAL(10,2)
);
-- 每天保存所有客户的完整状态
-- 优点:查询历史任意一天状态简单
-- 缺点:存储空间大

-- 模式2:流水表(仅变化)
CREATE TABLE customer_status_history (
    customer_id INT,
    old_status VARCHAR(20),
    new_status VARCHAR(20),
    change_time TIMESTAMP,
    change_reason VARCHAR(200)
);
-- 只记录状态变化
-- 优点:存储效率高
-- 缺点:查询历史状态需要回溯计算

-- 模式3:拉链表(最常用)
CREATE TABLE customer_chain (
    customer_id INT,
    customer_status VARCHAR(20),
    start_date DATE,
    end_date DATE,
    is_current BOOLEAN
);
-- 示例数据:
-- | customer_id | status | start_date |  end_date  | is_current|
-- |-------------|--------|------------|------------|-----------|
-- | 1001        | 新客户  | 2024-01-01 | 2024-01-14 | false     |
-- | 1001        | 活跃    | 2024-01-15 | 2024-03-31 | false     |
-- | 1001        | 流失    | 2024-04-01 | 9999-12-31 | true      |

特性四:时变性(Time-Variant)

时间维度的三个层次
sql 复制代码
-- 1. 数据本身的时间属性(业务时间)
CREATE TABLE fact_orders (
    order_key BIGINT,
    customer_key INT,
    product_key INT,
    -- 业务时间维度
    order_date DATE,          -- 下单日期
    ship_date DATE,           -- 发货日期
    payment_date DATE,        -- 付款日期
    -- 技术时间维度
    load_date DATE,           -- 加载到数仓日期
    effective_date DATE,      -- 记录生效日期
    expiry_date DATE,         -- 记录失效日期
    -- 业务度量
    order_amount DECIMAL(10,2),
    quantity INT
);

-- 2. 时间作为分析维度(时间维度表)
CREATE TABLE dim_date (
    date_key INT PRIMARY KEY,      -- 20240115
    full_date DATE,                -- 2024-01-15
    day_of_month INT,              -- 15
    month_name VARCHAR(20),        -- January
    quarter INT,                   -- 1
    year INT,                      -- 2024
    is_weekend BOOLEAN,            -- false
    is_holiday BOOLEAN,            -- false
    holiday_name VARCHAR(50)       -- NULL
);

-- 3. 历史数据分析能力
-- 能回答的问题示例:
-- "比较今年和去年同期的销售增长率"
-- "分析促销活动对后续三个月销售的影响"
-- "识别客户生命周期中的关键转化点"
时变性的业务价值体现

场景:分析产品价格调整的市场反应

sql 复制代码
-- 生产系统只能看到当前价格
SELECT product_id, current_price FROM products;
-- 结果:product_001 → 299元

-- 数据仓库能看到完整价格历史
SELECT 
    p.product_name,
    ph.old_price,
    ph.new_price,
    ph.change_date,
    -- 分析价格变化后的销量变化
    SUM(CASE 
        WHEN o.order_date BETWEEN ph.change_date AND ph.change_date + 7 
        THEN o.quantity ELSE 0 
    END) as week_after_sales,
    SUM(CASE 
        WHEN o.order_date BETWEEN ph.change_date - 7 AND ph.change_date - 1
        THEN o.quantity ELSE 0 
    END) as week_before_sales
FROM product_price_history ph
JOIN dim_product p ON ph.product_key = p.product_key
LEFT JOIN fact_orders o ON p.product_key = o.product_key
GROUP BY p.product_name, ph.old_price, ph.new_price, ph.change_date
ORDER BY ph.change_date DESC;

-- 能够发现:价格从299降到249时,销量增长40%
-- 价格从249升回299时,销量下降25%

1.3 数仓 vs 业务数据库 vs 数据湖

三者的本质区别

维度 业务数据库 (OLTP) 数据仓库 (OLAP) 数据湖
主要用途 日常业务操作 数据分析决策 原始数据存储
数据结构 高度结构化 结构化 任意结构
数据新鲜度 实时 分钟~天级延迟 实时~批处理
数据粒度 明细事务级 明细+汇总级 原始数据
Schema策略 Schema-on-Write Schema-on-Write Schema-on-Read
典型操作 INSERT, UPDATE, DELETE SELECT (复杂查询) PUT, GET, 批处理
用户类型 业务操作人员 数据分析师/管理者 数据工程师/科学家
性能优化 高并发短事务 复杂查询吞吐量 存储成本与处理能力

技术架构对比图

复制代码
┌─────────────────────────────────────────────────────────┐
│                   数据生态系统全景图                       │
├─────────────────┬─────────────────┬─────────────────────┤
│   业务数据库      │    数据仓库       │       数据湖          │
│   (OLTP系统)     │    (OLAP系统)     │    (原始数据池)        │
├─────────────────┼─────────────────┼─────────────────────┤
│  MySQL          │  Snowflake      │  Amazon S3         │
│  PostgreSQL     │  Redshift       │  Azure Data Lake   │
│  Oracle         │  BigQuery       │  HDFS              │
│                 │  Teradata       │                     │
├─────────────────┼─────────────────┼─────────────────────┤
│  ● 在线订单      │  ● 销售报表       │  ● 网站点击日志        │
│  ● 用户注册      │  ● 客户分群       │  ● 服务器监控数据       │
│  ● 库存更新      │  ● 趋势分析       │  ● IoT传感器数据       │
│  ● 支付处理      │  ● 预测模型       │  ● 社交媒体文本        │
├─────────────────┼─────────────────┼─────────────────────┤
│  写入优化        │   读取优化        │    存储优化           │
│  高并发          │   复杂查询        │    低成本            │
│  ACID事务        │   批量加载        │    高扩展性          │
└─────────────────┴─────────────────┴─────────────────────┘
         │                  │                  │
         ↓                  ↓                  ↓
    ┌─────────────────────────────────────────────┐
    │          统一数据服务层 (Data API)            │
    ├─────────────────────────────────────────────┤
    │  ● 实时仪表盘      ● 自助分析       ● 数据科学平台  │
    │  ● 移动报表        ● 预测预警       ● AI模型训练   │
    └─────────────────────────────────────────────┘

查询模式对比示例

sql 复制代码
-- 场景:分析2023年Q4各产品类别的销售情况

-- 1. 业务数据库查询(痛苦)
SELECT 
    c.category_name,
    SUM(oi.quantity * oi.unit_price) as total_sales
FROM orders o
JOIN order_items oi ON o.order_id = oi.order_id
JOIN products p ON oi.product_id = p.product_id
JOIN categories c ON p.category_id = c.category_id
LEFT JOIN customers cust ON o.customer_id = cust.customer_id
LEFT JOIN promotions prom ON o.promotion_id = prom.promotion_id
WHERE o.order_date BETWEEN '2023-10-01' AND '2023-12-31'
    AND o.status IN ('COMPLETED', 'SHIPPED')
    AND o.payment_status = 'PAID'
GROUP BY c.category_name
ORDER BY total_sales DESC;
-- 问题:8表关联,影响线上业务,查询耗时30秒+

-- 2. 数据仓库查询(高效)
SELECT 
    cat.category_name,
    SUM(f.sales_amount) as total_sales
FROM fact_sales f
JOIN dim_date d ON f.date_key = d.date_key
JOIN dim_product p ON f.product_key = p.product_key
JOIN dim_category cat ON p.category_key = cat.category_key
WHERE d.year = 2023 AND d.quarter = 4
    AND f.order_status = '已完成'
GROUP BY cat.category_name
ORDER BY total_sales DESC;
-- 优势:3表关联,预聚合,查询耗时<2秒

-- 3. 数据湖查询(原始分析)
-- 使用Spark SQL或Presto查询Parquet文件
spark.sql("""
    SELECT 
        get_json_object(log, '$.category') as category,
        COUNT(*) as event_count,
        SUM(CAST(get_json_object(log, '$.amount') AS DOUBLE)) as total_amount
    FROM raw_sales_logs
    WHERE dt BETWEEN '2023-10-01' AND '2023-12-31'
        AND get_json_object(log, '$.event_type') = 'purchase'
    GROUP BY get_json_object(log, '$.category')
""")
-- 特点:处理半结构化数据,灵活性高

现代架构的融合趋势:湖仓一体

复制代码
传统模式的问题:
数据湖 → (ETL) → 数据仓库 → BI工具
问题:数据移动成本高,存在数据冗余

湖仓一体架构:
        ┌─────────────────────┐
        │     统一元数据层      │
        └──────────┬──────────┘
                   │
    ┌──────────────┼──────────────┐
    │              │              │
    ▼              ▼              ▼
┌─────────┐  ┌─────────┐  ┌─────────┐
│ 事务数据  │  │ 分析数据  │  │ 原始数据  │
│ (Delta) │  │ (Delta) │  │ (Parquet)│
└─────────┘  └─────────┘  └─────────┘
    │              │              │
    └──────────────┼──────────────┘
                   ▼
           ┌──────────────┐
           │  统一查询引擎  │
           │ (Spark/Trino)│
           └──────────────┘
                   │
                   ▼
           ┌──────────────┐
           │ 多种工作负载  │
           │ BI + AI + ...│
           └──────────────┘

核心优势:
1. 单一副本:数据不移动,通过统一元数据管理
2. 多种工作负载:支持BI、数据科学、实时分析
3. 开放格式:使用Parquet、Delta、Iceberg等开放格式
4. ACID事务:保证数据一致性

本章小结与关键收获

  1. 数据仓库的核心价值:解决业务系统中的四个数据困境

    • 分析性能问题
    • 数据不一致问题
    • 分析延迟问题
    • 历史数据缺失问题
  2. 记住四个特性:面向主题、集成性、非易失性、时变性

    • 这不仅是技术特性,更是业务价值的体现
    • 每个特性都对应着特定的业务需求
  3. 理解三者的定位

    • 业务数据库:支持业务运转(记录系统)
    • 数据仓库:支持业务决策(分析系统)
    • 数据湖:存储数据资产(资源系统)
  4. 现代趋势:从分离走向融合,湖仓一体成为主流选择


思考题:

  1. 你所在的公司是否存在本章提到的数据困境?具体表现是什么?
  2. 如果要建立数据仓库,你认为应该优先解决哪个业务痛点?为什么?
  3. 结合你的业务场景,设计一个简单的主题域划分方案。

下一章预告: 我们将深入数据仓库的内部架构,了解经典的三层架构和现代技术栈选择。

相关推荐
u0109272716 小时前
RESTful API设计最佳实践(Python版)
jvm·数据库·python
qq_1927798712 小时前
高级爬虫技巧:处理JavaScript渲染(Selenium)
jvm·数据库·python
u01092727112 小时前
使用Plotly创建交互式图表
jvm·数据库·python
爱学习的阿磊12 小时前
Python GUI开发:Tkinter入门教程
jvm·数据库·python
tudficdew13 小时前
实战:用Python分析某电商销售数据
jvm·数据库·python
TM1Club13 小时前
AI驱动的预测:新的竞争优势
大数据·人工智能·经验分享·金融·数据分析·自动化
zhang1338308907513 小时前
CG-09H 超声波风速风向传感器 加热型 ABS材质 重量轻 没有机械部件
大数据·运维·网络·人工智能·自动化
sjjhd65213 小时前
Python日志记录(Logging)最佳实践
jvm·数据库·python
Configure-Handler13 小时前
buildroot System configuration
java·服务器·数据库