1-项目概述与数仓基础
1 - 电商问数:项目概述与数仓基础
本章课程目标:
- 理解「电商问数」要解决的核心问题,以及它为什么属于典型的
NL2SQL问数项目。 - 区分业务数据库与数据仓库的定位差异,并建立“为什么企业分析通常不直接查业务库”的基础认识。
- 初步掌握事实表、维度表和维度建模的核心概念,同时对后续章节要解决的问题形成整体预期。
学习建议: 这一章先把业务问题看清楚,再看技术名词。重点理解自然语言问数为什么需要数据仓库、维度建模和指标口径支撑。读完后最好能用一个例子说清“事实表、维度表、指标”各自解决什么问题,这会直接影响后面你能不能看懂字段召回和 SQL 生成。
1、本章导读
1.1 项目简介
「电商问数」是一个面向企业数据分析场景的智能体项目。它要解决的问题非常明确:让用户不用手写 SQL,也能像聊天一样从数据仓库里获取想要的分析结果。
如果你是开发者、产品经理、运营,甚至是业务同学,通常都会遇到这样一种情况:
- 我想知道华北地区的销售总额是多少。
- 我想看各品牌销量最高的商品有哪些。
- 我想统计男性用户的订单总额。
这些问题本质上都不是“聊天问题”,而是数据查询问题。传统做法往往需要懂 SQL 的开发或数据分析师来完成;而「电商问数」要做的,就是把“自然语言提问”自动转换成“可执行的 SQL 查询”,再把查询结果返回给用户。
一句话概括,这个项目就是一个典型的 NL2SQL 智能体项目。NL2SQL 的意思是:Natural Language to SQL,也就是“自然语言转 SQL”。用户只负责提问,系统借助大模型理解问题并生成 SQL,再到数据库或数据仓库中执行查询,最后把结果返回给用户。
一句话记忆: 「电商问数」要做的事,就是把“人能看懂的问题”翻译成“数据库能执行的 SQL”。

2、问数项目背景
2.1 为什么企业需要「问数」系统
企业每天都在产生大量数据。以电商业务为例,用户在平台上的很多关键行为,都会沉淀成数据,例如:
- 浏览和收藏了哪些商品
- 把哪些商品加入购物车并最终下单
- 支付了多少钱,以及来自哪个地区、属于哪类用户
这些数据最开始是为了支撑业务系统本身而保存的。比如:
- 订单要保存下来,用户之后才能查看自己的订单记录
- 购物车要保存下来,用户下次打开 App 时才能看到之前加入的商品
- 用户信息要保存下来,系统才能识别当前用户是谁
但数据的价值显然不止于“把业务跑起来”。
当数据积累到一定规模后,我们还希望基于这些数据做进一步分析,例如:
- 哪个地区的销售额更高
- 哪些用户群体更有购买倾向,分析用户偏好
- 哪个品牌最近表现最好
这类问题已经不属于日常业务操作,而属于数据分析与经营决策的范畴。企业做“问数”系统,本质上就是为了把数据分析的门槛降下来,让更多人能够直接使用数据。
3、数据仓库基础
正式看事实表和维度表之前,先把几层数据关系放到一张图里。业务数据库负责支撑交易系统运行,教学数据仓库负责承接分析查询,元数据知识库则把“有哪些表、字段、指标和值”整理给问数智能体使用。
3.1 为什么不直接查业务数据库
你可能会问:既然业务数据本来就保存在数据库里,为什么不直接查业务库,而要单独搞一个“数据仓库”?原因很简单,因为业务数据库和分析型查询的目标并不一样。
业务数据库的核心任务是支撑线上业务稳定运行。它更关注下面三件事:下单要快,支付要快,查询订单详情要快。
而分析型查询通常具有下面这些特点:需要扫描大量数据,并做聚合计算,比如 sum、count、avg;经常要做多表关联、分组和排序;查询更重,对计算资源的消耗也更高。
如果把大量分析 SQL 直接跑在业务数据库上,就可能带来明显的问题:数据库负载升高;线上接口响应变慢;严重时甚至影响正常业务。
所以在真实企业环境里,通常会把业务数据同步到一个专门用于分析的系统中,这个系统就是数据仓库。
3.2 什么是数据仓库
你可以先把“数据仓库”理解成一个专门存放分析数据的大仓库。
它有三个关键特征:
- 它存的是来自业务系统的大量历史数据。
- 它主要服务于统计分析,而不是直接服务于线上交易。
- 它的数据组织方式,通常会为了“分析方便”而重新设计。
这意味着,数据仓库并不是把业务数据库原封不动搬过来,而是会在同步过程中进行整理、建模和加工,让后续分析更高效、更清晰。
在大数据体系里,数据仓库常见的实现方式有很多,比如 Hive。你可以先把 Hive 理解成一个面向数据仓库场景的分析引擎。它表面上也是让我们写 SQL,但底层会把这些 SQL 转换成分布式计算任务,交给大数据集群去执行,从而处理更大规模的分析任务。
不过本教程不会真的去部署 Hive。原因很简单:它通常还依赖 Hadoop 一类的大数据基础设施,环境较重,初学时很容易把注意力放到平台搭建上,而不是问数系统本身。
基于这个考虑,当前教学环境直接使用 MySQL 来模拟数据仓库场景。虽然底层没有真正使用 Hive,但项目的核心链路并没有变化,依然是生成 SQL、执行 SQL、返回分析结果。这样既能保留问数系统的核心思路,也能把环境复杂度控制在更适合学习的范围内。
所以可以把本项目理解为:用更容易上手的方式,完整体验一套企业级问数系统的核心设计。
简单来说:
数据仓库中的数据通常来源于业务数据库,但并不是把业务库中的表和数据原封不动、一比一地搬过来。
它通常会根据分析场景进行清洗、整理和重新建模。
业务数据库主要是为了支撑日常业务运行,数据仓库则主要是为了做统计分析、数据洞察和业务决策。
3.3 业务数据库和数据仓库区别
如果前面几段你已经看懂了,可以再用下面这张表快速对照一次:
| 对比项 | 业务数据库 | 数据仓库 |
|---|---|---|
| 主要目标 | 支撑日常业务运行 | 支撑统计分析与经营决策 |
| 常见场景 | 下单、支付、查订单、改库存 | 按地区统计销售额、按品牌分析销量、做用户画像 |
| 常见类型 | 通常是关系型数据库,比如 MySQL |
常见实现有 Hive,本项目中用 MySQL 来模拟 |
| 查询特点 | 单条查询多、写入频繁、强调高并发 | 聚合计算多、扫描数据量大、多表关联多 |
| 设计重点 | 保证一致性、响应快、写入稳 | 方便统计、方便分组、方便分析 |
| 数据来源 | 直接来自业务系统 | 通常来源于业务系统同步后的整理结果 |
| 建模方式 | 更偏规范化设计 | 更常见维度建模 |
一句话记忆: 业务数据库是为了把业务“跑起来”,数据仓库是为了把数据“用起来”。
3.4 数据仓库为什么更适合分析
数据仓库之所以适合分析,不只是因为它和业务库分开了,更重要的是它通常会采用更适合分析的建模方式。
在业务数据库中,表结构往往遵循关系型数据库的规范化设计,目标是:
- 减少冗余
- 保证数据一致性
- 服务日常业务写入与查询
但在分析场景中,我们更看重的是:
- 指标能不能方便统计
- 维度能不能方便筛选和分组
- SQL 能不能写得清楚、稳定、好维护
因此,数据仓库中常见的一种建模方式叫做维度建模。
这里也可以顺带建立一个更清楚的理解:从业务数据库到数据仓库,通常还会经历抽取、清洗、转换等数据加工过程,而建模更贴切地说,是其中“根据分析需求重新设计表结构”的这一步。
4、维度建模入门
4.1 维度建模简介
维度建模是数仓里非常经典的一种设计方法。简单来说,它就是围绕“分析一件业务事件需要看哪些指标、从哪些角度观察”来设计表结构。
在这种设计里,表通常会被分成两大类:事实表、维度表。
可以先把握这两个核心定义:
- 事实表:记录“发生了什么事”
- 维度表:描述“这件事发生在什么对象、什么时间、什么地点、什么属性下”
4.2 常见建模方式
在企业数据体系里,建模方式并不只有维度建模。不同建模方式,通常服务于不同目标。
| 建模方式 | 主要面向什么场景 | 核心特点 | 适不适合本项目 |
|---|---|---|---|
ER 建模 / 规范化建模 |
业务数据库、交易系统 | 强调减少冗余、保证一致性,典型是 3NF 设计 |
不太适合作为问数系统的主模型 |
| 维度建模 | 数据仓库、报表分析、BI、问数系统 | 把表拆成事实表和维度表,查询思路清晰 | 最适合本项目 |
Data Vault 建模 |
企业级数仓底层、复杂多源接入 | 强调可追溯、可扩展、便于持续接入新数据 | 更适合底层建设,不适合作为入门主线 |
| 宽表建模 / 反规范化建模 | 固定报表、高频分析查询 | 把常用字段尽量提前摊平,减少查询时的 join |
可作为补充手段,但不是本项目重点 |
先把它们之间的关系记成一句话会更方便:业务库更常见的是 ER/规范化建模,分析场景更常见的是维度建模,而大型企业数仓底层有时也会使用 Data Vault 建模。
本教程之所以重点讲维度建模,是因为「电商问数」要解决的是“如何更自然地做统计分析和生成 SQL”,而在这个目标下,维度建模最直观,也最适合作为分析型查询的入门思路。
4.3 当前项目数仓模型说明
下面这张图展示的是当前项目中,数据仓库采用维度建模后的事实表与维度表示例。可以结合这张简化示意图理解它们之间的关系:

在本项目的教学环境中,我们用一张事实表 fact_order 和四张维度表 dim_product、dim_customer、dim_region、dim_date 来模拟一个简化后的电商数仓。
为了方便看懂这张图,这里先补充两个图例说明:
- 钥匙图标:表示该字段是主键,用来唯一标识表中的一条记录
- 红色圆点:表示该字段是重点维度字段,通常会作为筛选条件、分组字段,后续也会重点关注它们的字段取值
如果按字段角色来拆分,这个模型可以这样理解:
fact_order(事实表 订单信息):order_id是主键;customer_id、product_id、date_id、region_id是外键;order_quantity、order_amount是度量字段dim_product(维度表 商品信息):product_id是主键;product_name、category、brand是维度字段dim_customer(维度表 客户信息):customer_id是主键;customer_name、gender、member_level是维度字段dim_region(维度表 地区信息):region_id是主键;province、region_name、country是维度字段dim_date(维度表 时间信息):date_id是主键;year、quarter、month、day是维度字段
维度表通常是“1 个主键 + 多个维度字段”,事实表通常是“1 个主键 + 多个外键 + 多个度量字段”。
其中,fact_order 是核心事实表,它记录的是“下单”这件业务事件。每一行数据都对应一次订单行为,里面既包含关联各维度的外键,也包含可以统计的数值字段,例如:
- 四个外键:
customer_id、product_id、region_id、date_id,分别用来关联客户、商品、地区和日期维度 - 一个数量字段:
order_quantity,表示这笔订单买了多少件商品 - 一个金额字段:
order_amount,表示这笔订单的总金额
这里的 order_quantity 和 order_amount 这类数值字段,通常也叫度量值。它们正是后续统计分析时最常被聚合计算的对象。
而维度表的作用,是补充描述信息。例如,dim_product 表用来描述商品名称、品类和品牌,dim_customer 表用来描述客户姓名、性别和会员等级,dim_region 表用来描述省份、大区和国家,dim_date 表用来描述年、季度、月、日等时间属性。
这里还需要特别说明一点:当前教程中的模型是一个为了教学而简化过的数仓示例。在真实企业的数据仓库中,通常不会只有一张事实表。因为企业中的业务事件往往不止“下单”这一种,还可能包括加购、支付、退单、收藏、浏览等多种行为。一般来说,每一种核心业务事件都可能对应一张独立的事实表。
也就是说,真实数仓里常见的情况往往是:
- 有多张事实表,分别记录不同类型的业务事件
- 这些事实表会共用一部分维度表
- 用户、商品、地区、时间等维度,往往会在多个事实表之间重复使用
例如,订单事实表、加购事实表、退单事实表,可能都会关联同一张客户维度表、同一张商品维度表、同一张地区维度表和同一张日期维度表。这样设计的好处是,不同业务事件可以分别统计,但观察这些事件时使用的分析视角又是统一的。
而在本教程里,为了让大家先把核心思路学明白,我们只保留了一张 fact_order 事实表和四张常用维度表。虽然模型被简化了,但它已经足够帮助我们理解智能问数系统最核心的分析逻辑。
4.4 当前项目数仓的表示例
如果你想再具体一点,先看看当前教学环境中的 4 张典型数仓表。下面每张表都会展示完整字段名作为表头,并给出 3 行示例数据,帮助你先建立直观认识。
dim_customer 示例数据:
| customer_id(客户 ID,主键) | customer_name(客户名称,维度字段) | gender(性别,维度字段) | member_level(会员等级,维度字段) |
|---|---|---|---|
C001 |
李伟 |
男 |
黄金 |
C002 |
王芳 |
女 |
白银 |
C003 |
张敏 |
女 |
铂金 |
dim_product 示例数据:
| product_id(商品 ID,主键) | product_name(商品名称,维度字段) | category(品类,维度字段) | brand(品牌,维度字段) |
|---|---|---|---|
P001 |
iPhone 15 Pro |
手机数码 |
苹果 |
P002 |
Galaxy S24 Ultra |
手机数码 |
三星 |
P003 |
戴森 V15 吸尘器 |
家用电器 |
戴森 |
dim_region 示例数据:
| region_id(地区 ID,主键) | province(省份,维度字段) | region_name(大区名称,维度字段) | country(国家,维度字段) |
|---|---|---|---|
R001 |
广东省 |
华南 |
中国 |
R002 |
浙江省 |
华东 |
中国 |
R003 |
北京市 |
华北 |
中国 |
fact_order 示例数据:
| order_id(订单 ID,主键) | customer_id(客户 ID,外键) | product_id(商品 ID,外键) | date_id(日期 ID,外键) | region_id(地区 ID,外键) | order_quantity(下单数量,度量字段) | order_amount(下单金额,度量字段) |
|---|---|---|---|---|---|---|
ORD20250101001 |
C001 |
P001 |
20250101 |
R001 |
1 |
8999.0 |
ORD20250102001 |
C002 |
P002 |
20250102 |
R002 |
2 |
6999.0 |
ORD20250103001 |
C003 |
P003 |
20250103 |
R003 |
1 |
3999.0 |
先记住一句话就够了:事实表负责记录业务事件,维度表负责补充事件的观察角度。 后面在第 2 章讲到同步脚本、元数据知识库和字段召回时,本质上都是围绕这些原始数仓表展开的。
4.5 用几个问题理解事实表和维度表
假设我们要问这样几个问题:各地区销售总额是多少?各品牌销售总额是多少?男性用户的订单总额是多少?
你会发现,这些问题虽然表面不同,但写 SQL 的思路很相似:
- 先找到承载核心指标的事实表,比如
fact_order - 再根据分析维度,关联对应的维度表
- 最后做筛选、分组、聚合统计
例如:
- 统计各地区销售额,要关联
dim_region - 统计各品牌销售额,要关联
dim_product - 统计男性用户订单总额,要关联
dim_customer
如果你想先快速抓住这三个问题的共性,也可以先看下面这张总表:
| 问题 | 事实表 | 维度表 | 关联字段 | 统计字段 | 聚合方式 |
|---|---|---|---|---|---|
| 各地区销售总额 | fact_order |
dim_region |
fact_order.region_id = dim_region.region_id |
fact_order.order_amount |
sum |
| 各品牌销售总额 | fact_order |
dim_product |
fact_order.product_id = dim_product.product_id |
fact_order.order_amount |
sum |
| 男性用户订单总额 | fact_order |
dim_customer |
fact_order.customer_id = dim_customer.customer_id |
fact_order.order_amount |
sum |
如果结合上面的示意图来看,这几个问题可以进一步拆成下面这样的查询思路:
- 各地区销售总额是多少
先从 fact_order 中取出订单金额字段 order_amount,再通过 fact_order.region_id = dim_region.region_id 关联地区维度表 dim_region,按 dim_region.region_name 分组,最后对 fact_order.order_amount 做 sum 聚合。
- 各品牌销售总额是多少
这个问题和上一个问题的思路完全类似,只不过分析维度从“地区”变成了“品牌”。因此仍然从 fact_order 出发,使用 fact_order.product_id = dim_product.product_id 关联商品维度表 dim_product,按 dim_product.brand 分组,再对 fact_order.order_amount 做 sum 聚合。
- 男性用户的订单总额是多少
这个问题的关键在于“男性用户”是一个筛选条件,而不是分组维度。因此我们还是从 fact_order 出发,通过 fact_order.customer_id = dim_customer.customer_id 关联客户维度表 dim_customer,然后用 dim_customer.gender = '男' 作为过滤条件,最后对 fact_order.order_amount 做 sum 聚合。
如果将来问题中再加上时间条件,比如“统计 2024 年各地区销售总额”,那就在原有基础上再关联 dim_date,通过 fact_order.date_id = dim_date.date_id 关联后,使用 dim_date.year = 2024 作为过滤条件即可。
这就是维度建模的价值所在:它让分析型 SQL 的结构更稳定、思路更统一,也更适合交给智能体去理解和生成。
如果把上面的例子再压缩成一个通用模板,其实大多数分析型 SQL 都可以按下面这个思路去理解:
1 | select 维度字段, 聚合函数(度量字段) |
在「电商问数」里,大模型最终也是在做类似这样的事:先判断事实表是谁、维度表是谁、过滤条件落在哪个字段上,再把这些关系组织成 SQL。
5、项目定位
5.1 「电商问数」主要做了什么
理解了数据仓库和维度建模之后,我们再来看「电商问数」的核心目标,就会更清楚。
很多同学第一次接触 NL2SQL 时,都会先想到一个最直接的做法:把用户的问题直接交给大模型,再配上一句提示词,比如“请帮我把这个问题转换成 SQL”,让模型自己生成查询语句。
这个思路听起来很自然,但在真实项目里通常是不够的。原因很简单:大模型本身并不知道你当前的数据仓库里到底有哪些表、每张表里有哪些字段、字段之间如何关联、哪些字段是维度、哪些字段是度量值。如果缺少这些上下文,它就很难稳定地生成正确 SQL。
那我们是不是可以把整个数据仓库的表结构、字段信息一次性全都塞进提示词里,再交给大模型呢?这看起来像是一个解决办法,但实际也存在明显问题:
- 数据仓库中的表通常很多,字段也很多,提示词很容易变得过长
- 大模型的上下文长度有限,表和字段太多时容易超出上限
- 即使没有超出上限,过多的无关信息也会干扰模型判断,并拖慢响应速度
所以,真正可行的思路并不是“把所有表都交给大模型”,而是先为数据仓库构建一套元数据知识库,再围绕用户问题召回最相关的表、字段、字段取值和指标说明,然后把这些上下文交给大模型生成 SQL。
如果你现在对“元数据知识库到底是什么、它和数据仓库是什么关系、系统具体是怎么召回的”还没有完全看懂,也没有关系。这里先抓住主线就够了:数据仓库负责存放分析数据,元数据知识库负责帮助智能体理解这些数据,智能体再基于两者完成查数。
更细的系统架构、同步过程、召回机制和执行流程,会在下一章 第 2 章 项目整体架构与智能体流程 里展开说明。
比如用户输入:
- 统计华北地区的销售总额
- 统计各地区销量排行前三的商品
系统最终要做到的,不是泛泛而谈地“解释问题”,而是真正生成可以执行的 SQL,并给出可靠结果。这就是“问数智能体”和普通聊天助手最大的区别。
也可以把它看作一种先检索相关元数据,再生成 SQL 的思路。后面讲系统架构时,我们再正式展开它和 RAG 思路之间的关系。也正因为如此,本项目后续的重点不只是“怎么调用大模型”,更重要的是“怎么把元数据组织好、检索好、利用好”。
5.2 一个最小可理解的问数链路
如果你想先用一句话把整个项目记住,可以先记下面这条最小链路:
- 用户先提一个自然语言问题,例如“统计华北地区的销售总额”。
- 系统先从元数据知识库里找出和这个问题最相关的字段、字段值和指标定义。
- 大模型基于这些上下文生成 SQL。
- 系统先校验 SQL,必要时再自动纠错。
- SQL 通过后再真正执行,并把查询结果返回给用户。
这条最小链路,其实就是整套项目后续章节的主线:用户提问,系统先找知识,再生成 SQL,最后执行查询。
5.3 本教程你将学到什么
通过这个项目,你将不只是学会“做一个能聊天的页面”,而是会系统接触到企业级智能体项目中的一整套关键能力:
- 如何围绕真实数仓场景设计一套可用的元数据知识库
- 如何把字段、指标和字段取值召回出来,并整理成适合大模型使用的上下文
- 如何用结构化智能体流程把生成、校验、执行和前后端交互串起来
如果你之前没有做过数据仓库项目,也不用担心。本教程会尽量按照“先理解场景,再理解设计,最后理解代码”的顺序展开,尽量让每一步都能跟得上。
本章小结:
- 问数系统的目标,是把自然语言提问转成可执行的 SQL 查询。
- 企业分析通常不会直接查业务数据库,而是会把数据整理进更适合分析的数据仓库,并通过维度建模让统计逻辑更清晰。
- 「电商问数」的关键,不是让大模型盲猜 SQL,而是先通过元数据知识库把相关上下文准备好,再让模型生成 SQL。
到这里,第 1 章的内容就结束了。接下来我们会进入下一份文档《第二章 项目整体架构与智能体流程》,系统梳理「电商问数」的元数据知识库设计、索引设计、同步脚本以及问数智能体流程。