1 - 电商问数:项目概述与数仓基础


本章课程目标:

  • 理解「电商问数」要解决的核心问题,以及它为什么属于典型的 NL2SQL 问数项目。
  • 区分业务数据库数据仓库的定位差异,并建立“为什么企业分析通常不直接查业务库”的基础认识。
  • 初步掌握事实表维度表维度建模的核心概念,同时对后续章节要解决的问题形成整体预期。

学习建议: 这一章先把业务问题看清楚,再看技术名词。重点理解自然语言问数为什么需要数据仓库、维度建模和指标口径支撑。读完后最好能用一个例子说清“事实表、维度表、指标”各自解决什么问题,这会直接影响后面你能不能看懂字段召回和 SQL 生成。


1、本章导读

1.1 项目简介

「电商问数」是一个面向企业数据分析场景的智能体项目。它要解决的问题非常明确:让用户不用手写 SQL,也能像聊天一样从数据仓库里获取想要的分析结果。

如果你是开发者、产品经理、运营,甚至是业务同学,通常都会遇到这样一种情况:

  • 我想知道华北地区的销售总额是多少。
  • 我想看各品牌销量最高的商品有哪些。
  • 我想统计男性用户的订单总额。

这些问题本质上都不是“聊天问题”,而是数据查询问题。传统做法往往需要懂 SQL 的开发或数据分析师来完成;而「电商问数」要做的,就是把“自然语言提问”自动转换成“可执行的 SQL 查询”,再把查询结果返回给用户。

一句话概括,这个项目就是一个典型的 NL2SQL 智能体项目。NL2SQL 的意思是:Natural Language to SQL,也就是“自然语言转 SQL”。用户只负责提问,系统借助大模型理解问题并生成 SQL,再到数据库或数据仓库中执行查询,最后把结果返回给用户。

一句话记忆: 「电商问数」要做的事,就是把“人能看懂的问题”翻译成“数据库能执行的 SQL”。

电商问数查询结果页:展示 LangGraph 执行流程、SQL 闭环进度和最终 GMV 查询结果


2、问数项目背景

2.1 为什么企业需要「问数」系统

企业每天都在产生大量数据。以电商业务为例,用户在平台上的很多关键行为,都会沉淀成数据,例如:

  • 浏览和收藏了哪些商品
  • 把哪些商品加入购物车并最终下单
  • 支付了多少钱,以及来自哪个地区、属于哪类用户

这些数据最开始是为了支撑业务系统本身而保存的。比如:

  • 订单要保存下来,用户之后才能查看自己的订单记录
  • 购物车要保存下来,用户下次打开 App 时才能看到之前加入的商品
  • 用户信息要保存下来,系统才能识别当前用户是谁

但数据的价值显然不止于“把业务跑起来”。

当数据积累到一定规模后,我们还希望基于这些数据做进一步分析,例如:

  • 哪个地区的销售额更高
  • 哪些用户群体更有购买倾向,分析用户偏好
  • 哪个品牌最近表现最好

这类问题已经不属于日常业务操作,而属于数据分析与经营决策的范畴。企业做“问数”系统,本质上就是为了把数据分析的门槛降下来,让更多人能够直接使用数据。


3、数据仓库基础

正式看事实表和维度表之前,先把几层数据关系放到一张图里。业务数据库负责支撑交易系统运行,教学数据仓库负责承接分析查询,元数据知识库则把“有哪些表、字段、指标和值”整理给问数智能体使用。

业务数据库、教学数据仓库、元数据知识库和问数智能体之间的关系

3.1 为什么不直接查业务数据库

你可能会问:既然业务数据本来就保存在数据库里,为什么不直接查业务库,而要单独搞一个“数据仓库”?原因很简单,因为业务数据库和分析型查询的目标并不一样。

业务数据库的核心任务是支撑线上业务稳定运行。它更关注下面三件事:下单要快,支付要快,查询订单详情要快。

而分析型查询通常具有下面这些特点:需要扫描大量数据,并做聚合计算,比如 sumcountavg;经常要做多表关联、分组和排序;查询更重,对计算资源的消耗也更高。

如果把大量分析 SQL 直接跑在业务数据库上,就可能带来明显的问题:数据库负载升高;线上接口响应变慢;严重时甚至影响正常业务。

所以在真实企业环境里,通常会把业务数据同步到一个专门用于分析的系统中,这个系统就是数据仓库

3.2 什么是数据仓库

你可以先把“数据仓库”理解成一个专门存放分析数据的大仓库。

它有三个关键特征:

  1. 它存的是来自业务系统的大量历史数据。
  2. 它主要服务于统计分析,而不是直接服务于线上交易。
  3. 它的数据组织方式,通常会为了“分析方便”而重新设计。

这意味着,数据仓库并不是把业务数据库原封不动搬过来,而是会在同步过程中进行整理、建模和加工,让后续分析更高效、更清晰。

在大数据体系里,数据仓库常见的实现方式有很多,比如 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_customer、dim_product、dim_region、dim_date 等维度表关系

在本项目的教学环境中,我们用一张事实表 fact_order 和四张维度表 dim_productdim_customerdim_regiondim_date 来模拟一个简化后的电商数仓。

为了方便看懂这张图,这里先补充两个图例说明:

  • 钥匙图标:表示该字段是主键,用来唯一标识表中的一条记录
  • 红色圆点:表示该字段是重点维度字段,通常会作为筛选条件、分组字段,后续也会重点关注它们的字段取值

如果按字段角色来拆分,这个模型可以这样理解:

  • fact_order(事实表 订单信息):order_id 是主键;customer_idproduct_iddate_idregion_id 是外键;order_quantityorder_amount 是度量字段
  • dim_product(维度表 商品信息):product_id 是主键;product_namecategorybrand 是维度字段
  • dim_customer(维度表 客户信息):customer_id 是主键;customer_namegendermember_level 是维度字段
  • dim_region(维度表 地区信息):region_id 是主键;provinceregion_namecountry 是维度字段
  • dim_date(维度表 时间信息):date_id 是主键;yearquartermonthday 是维度字段

维度表通常是“1 个主键 + 多个维度字段”,事实表通常是“1 个主键 + 多个外键 + 多个度量字段”。

其中,fact_order 是核心事实表,它记录的是“下单”这件业务事件。每一行数据都对应一次订单行为,里面既包含关联各维度的外键,也包含可以统计的数值字段,例如:

  • 四个外键:customer_idproduct_idregion_iddate_id,分别用来关联客户、商品、地区和日期维度
  • 一个数量字段:order_quantity,表示这笔订单买了多少件商品
  • 一个金额字段:order_amount,表示这笔订单的总金额

这里的 order_quantityorder_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 的思路很相似:

  1. 先找到承载核心指标的事实表,比如 fact_order
  2. 再根据分析维度,关联对应的维度表
  3. 最后做筛选、分组、聚合统计

例如:

  • 统计各地区销售额,要关联 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_amountsum 聚合。

  • 各品牌销售总额是多少

这个问题和上一个问题的思路完全类似,只不过分析维度从“地区”变成了“品牌”。因此仍然从 fact_order 出发,使用 fact_order.product_id = dim_product.product_id 关联商品维度表 dim_product,按 dim_product.brand 分组,再对 fact_order.order_amountsum 聚合。

  • 男性用户的订单总额是多少

这个问题的关键在于“男性用户”是一个筛选条件,而不是分组维度。因此我们还是从 fact_order 出发,通过 fact_order.customer_id = dim_customer.customer_id 关联客户维度表 dim_customer,然后用 dim_customer.gender = '男' 作为过滤条件,最后对 fact_order.order_amountsum 聚合。

如果将来问题中再加上时间条件,比如“统计 2024 年各地区销售总额”,那就在原有基础上再关联 dim_date,通过 fact_order.date_id = dim_date.date_id 关联后,使用 dim_date.year = 2024 作为过滤条件即可。

这就是维度建模的价值所在:它让分析型 SQL 的结构更稳定、思路更统一,也更适合交给智能体去理解和生成。

如果把上面的例子再压缩成一个通用模板,其实大多数分析型 SQL 都可以按下面这个思路去理解:

1
2
3
4
5
select 维度字段, 聚合函数(度量字段)
from 事实表
join 维度表 on 事实表.外键 = 维度表.主键
where 过滤条件
group by 维度字段

在「电商问数」里,大模型最终也是在做类似这样的事:先判断事实表是谁维度表是谁过滤条件落在哪个字段上,再把这些关系组织成 SQL。


5、项目定位

5.1 「电商问数」主要做了什么

理解了数据仓库和维度建模之后,我们再来看「电商问数」的核心目标,就会更清楚。

很多同学第一次接触 NL2SQL 时,都会先想到一个最直接的做法:把用户的问题直接交给大模型,再配上一句提示词,比如“请帮我把这个问题转换成 SQL”,让模型自己生成查询语句。

这个思路听起来很自然,但在真实项目里通常是不够的。原因很简单:大模型本身并不知道你当前的数据仓库里到底有哪些表、每张表里有哪些字段、字段之间如何关联、哪些字段是维度、哪些字段是度量值。如果缺少这些上下文,它就很难稳定地生成正确 SQL。

那我们是不是可以把整个数据仓库的表结构、字段信息一次性全都塞进提示词里,再交给大模型呢?这看起来像是一个解决办法,但实际也存在明显问题:

  • 数据仓库中的表通常很多,字段也很多,提示词很容易变得过长
  • 大模型的上下文长度有限,表和字段太多时容易超出上限
  • 即使没有超出上限,过多的无关信息也会干扰模型判断,并拖慢响应速度

所以,真正可行的思路并不是“把所有表都交给大模型”,而是先为数据仓库构建一套元数据知识库,再围绕用户问题召回最相关的表、字段、字段取值和指标说明,然后把这些上下文交给大模型生成 SQL。

如果你现在对“元数据知识库到底是什么、它和数据仓库是什么关系、系统具体是怎么召回的”还没有完全看懂,也没有关系。这里先抓住主线就够了:数据仓库负责存放分析数据,元数据知识库负责帮助智能体理解这些数据,智能体再基于两者完成查数。

更细的系统架构、同步过程、召回机制和执行流程,会在下一章 第 2 章 项目整体架构与智能体流程 里展开说明。

比如用户输入:

  • 统计华北地区的销售总额
  • 统计各地区销量排行前三的商品

系统最终要做到的,不是泛泛而谈地“解释问题”,而是真正生成可以执行的 SQL,并给出可靠结果。这就是“问数智能体”和普通聊天助手最大的区别。

也可以把它看作一种先检索相关元数据,再生成 SQL 的思路。后面讲系统架构时,我们再正式展开它和 RAG 思路之间的关系。也正因为如此,本项目后续的重点不只是“怎么调用大模型”,更重要的是“怎么把元数据组织好、检索好、利用好”。

5.2 一个最小可理解的问数链路

如果你想先用一句话把整个项目记住,可以先记下面这条最小链路:

  1. 用户先提一个自然语言问题,例如“统计华北地区的销售总额”。
  2. 系统先从元数据知识库里找出和这个问题最相关的字段、字段值和指标定义。
  3. 大模型基于这些上下文生成 SQL。
  4. 系统先校验 SQL,必要时再自动纠错。
  5. SQL 通过后再真正执行,并把查询结果返回给用户。

这条最小链路,其实就是整套项目后续章节的主线:用户提问,系统先找知识,再生成 SQL,最后执行查询。

5.3 本教程你将学到什么

通过这个项目,你将不只是学会“做一个能聊天的页面”,而是会系统接触到企业级智能体项目中的一整套关键能力:

  • 如何围绕真实数仓场景设计一套可用的元数据知识库
  • 如何把字段、指标和字段取值召回出来,并整理成适合大模型使用的上下文
  • 如何用结构化智能体流程把生成、校验、执行和前后端交互串起来

如果你之前没有做过数据仓库项目,也不用担心。本教程会尽量按照“先理解场景,再理解设计,最后理解代码”的顺序展开,尽量让每一步都能跟得上。


本章小结:

  • 问数系统的目标,是把自然语言提问转成可执行的 SQL 查询
  • 企业分析通常不会直接查业务数据库,而是会把数据整理进更适合分析的数据仓库,并通过维度建模让统计逻辑更清晰。
  • 「电商问数」的关键,不是让大模型盲猜 SQL,而是先通过元数据知识库把相关上下文准备好,再让模型生成 SQL。

到这里,第 1 章的内容就结束了。接下来我们会进入下一份文档《第二章 项目整体架构与智能体流程》,系统梳理「电商问数」的元数据知识库设计、索引设计、同步脚本以及问数智能体流程。