跳到主要内容

表格和数据库 — 三条完全不同的路,选错就答错

这一章讲三件事: 为什么表格不能照着文章那样处理; 书里给的三条路各自能回答什么问题、答不了什么问题; 以及选错路时系统为什么不会报错,只会给你一个错答案。 位置:仍在入库线的第一步,但表格是这一步里唯一一类「换个思路就得换整套架构」的资料。

1. 先看现象:同一张表,三个问题,只有一条路全答得对

假设你有一张员工信息表,四万八千行、十五列(年龄、部门、学历、职位、收入……)—— 这正是书里示例用的那份公开数据的规模1。 用户可能这么问:

问题长什么样
「64 号候选人的学历是什么?」——只看一行就够
「这张表大概讲了什么?哪几列比较重要?」——要看全貌,但不用算
「本科学历的人里,年收入超过 5 万的占多大比例?」——必须把四万八千行都数一遍

第 01 章那套办法(切块、变成一串数、比谁离得近)只能答对甲。

乙勉强,丙必然错——而且不是「答不上来」,是给你一个像模像样的百分比。 因为检索只会搬来几十行「看起来最相关」的记录,模型照着这几十行算了个比例, 它不知道自己只看到了千分之一都不到的数据。

这一章讲的就是:怎么让丙这类问题也答得对。

2. 顶层全景:一张决策图

用户会问什么样的问题?

├─ 查具体某一条 ────────→ 路一:把每一行改写成一句话,当普通文字入库

├─ 看整张表的样子 ──────→ 路二:把整张表直接贴进给模型的话里(表得小)

└─ 加总/算比例/排名 ────→ 路三:把表放进数据库,让模型写一句查询语句去算

图说:书里明说这是「三种根本不同的方式」,不是三个可以叠着用的技巧。
决定用哪条的不是你的数据,是你的用户会问什么。

书里的原话就是这个意思:处理 Excel 和 CSV——最朴素的那种表格文件, 每行一条记录、列与列之间用逗号隔开,用记事本就能打开—— 有三种根本不同的方式2

还有一个词后面反复用:加总(行话叫「聚合」)就是把很多行合成一个数—— 求和、算平均、算比例、数个数,都算。

3. 为什么表格是个特例

书里在讨论里点破了根子上的原因,一句话:

大语言模型和嵌入模型,都是为「文字」设计的3

表格不是文字。 一张表的意思有一半藏在排版里: 第 3 行第 5 列的那个「1」,只有对照着列头「是否本科」才有意义。 你把它抽成纯文字,列与列的对应关系就散了。

一张表: 姓名 年龄 部门 收入
张三 34 供应链 62000

粗暴抽成文字: 张三 34 供应链 62000
↑ 34 是什么?年龄?工龄?工号?—— 对应关系没了

图说:表格的信息一半在格子里,一半在「哪个格子对哪个列头」里。后者是最容易丢的。

三条路,本质上是三种不同的「把对应关系保住」的办法。

4. 路一:把每一行改写成一句话

怎么做

书里给的说明特别好懂:

把 CSV 的每一行变成一句能独立成立的话想象你在跟一个从没见过这份数据的小孩解释它—— 拿一行出来,对着列头,用大白话说清这些数字是什么意思4

例子:

原始一行: age=34, workclass=供应链, education=本科, income=>50K

改写之后: 「这位候选人 34 岁,在供应链部门工作,拥有本科学历,年收入超过 5 万。」

图说:列头被写进了句子里。对应关系从「排版」搬进了「文字」——这才是这一招真正做的事。

改写完之后,这些句子就是普通文字了,走第 01 章那条线:切块、算嵌入、进库5

书里的示例用的是一份公开的「人口收入」数据集,15 列、48000 行, 用一个固定模板给每一行套出一句话1

它能答什么、答不了什么

书里自己写得很直白:

路一实现起来很直接,但它只能用「少数几行」里的信息来回答问题。6

这就是第 1 节里问题丙必错的原因。 检索一次只搬回来几块, 每块是一行(或几行),模型手里永远只有几行

判断(我们的,不是书里的): 这一条限制不是「效果差一点」,是一个安静的正确性缺陷。 用户问一个加总类的问题,系统会照样给出一个数,没有任何迹象表明这个数只基于千分之一都不到的样本。 所以只要你选了路一,就必须在产品层面拦住加总类的问题—— 要么识别出来转给路三,要么明确回答「这类问题我答不了」。 如果错,会错在: 如果你的表本来就只有几十行、一次检索能覆盖大半,那这个缺陷不显著。 判据是:表的行数,和你一次检索能取回的块数,差几个数量级。

一条书里点到、值得单独记的延伸

书里在「另见」里提到:同样这套「把行改写成句子」的办法也能用于分类任务, 并指向了一篇叫 TabLLM 的论文7

这篇论文的结论比书里说的更有意思: 把表格的一行改写成一句自然语言、 连同任务说明一起交给大模型,效果能追平提升树(处理表格数据的传统看家做法,长期是这类任务上最难打败的对手)——而且是在只给极少几个样例的情况下; 连一个样例都不给,也能有像样的表现7

为什么值得记: 它说明「把表格写成句子」不只是一个入库的权宜之计, 它本身就是一种让模型理解表格的有效方式——这为路一提供了书里没给出的依据。

5. 路二:整张表贴进去

怎么做

如果表不大,干脆把整张表塞进给模型的那段话里。 书里说大多数模型在直接读提示里的表格这件事上表现相当好, 前提是格式对——要把表转成 Markdown 的写法 (第 02 章讲过:用 #、竖线这类符号把排版直接写在纯文本里),让模型认出这是一张表8

为什么偏偏是它: 它的排版信息本身就是文字——竖线划出列,横线划出表头—— 所以能原样塞进给模型的那段话里,不像 Excel 那样一离开软件就散了架。

| 姓名 | 年龄 | 部门 |
|------|------|------|
| 张三 | 34 | 供应链 |

图说:竖线把「哪个值对哪个列头」重新钉住了。这就是路二保住对应关系的办法。

它能答什么、答不了什么

书里的评价同样直白:

路二对大表来说会很贵,而且应付不了复杂的查询。6

「贵」的原因回到第 01 章:提示越长,越慢越贵。一张几万行的表贴进去,每问一次付一次全表的钱。

6. 路三:让模型写一句查询语句

怎么做

对于要在整张表上做加总的问题,把表放进数据库, 然后让模型根据用户的问题生成一句 SQL(就是查询数据库用的那种语言)查询, 拿查询结果去拼提示9

SQL 长什么样: 它像英文句子—— 比如「从员工表里选出所有本科学历的人,数一数有多少个」。 关键在于:它是真的去算,不是去猜。

这套做法有个名字:Text-to-SQL,直译就是「从人话到查询语句」。

用户:「本科学历的人里,收入超过 5 万的占多大比例?」

├─ 模型写出一句查询: 数一数本科的有多少人、其中收入>5万的有多少人

├─ 数据库真的把 48000 行全数了一遍 → 返回两个数

└─ 把这两个数和原问题拼成提示 → 模型写出「约 41%」

图说:算数交给数据库,措辞交给模型。模型从来没看过那 48000 行。

(「约 41%」这个数、以及前面那位「张三 34 岁 供应链 62000」都是为演示编的,不是真实数值。 书里在这份数据上只给了两个数:15 列、48000 行。别把 41% 当成书里的实测结果引出去。)

书里补了一句实用信息:有 Vanna AI 这类库能让这件事实现起来比较简单10

这条路的代价,书里没写

判断(我们的,不是书里的): 路三把一个「检索问题」换成了一个「代码生成问题」, 而这带来两个书里一个字没提的风险: ① 模型可能写出一句写法完全合规、逻辑却是错的查询——比如漏掉一个筛选条件—— 结果同样是一个看起来很像的错数字; ② 让模型生成的语句直接连上生产数据库,是一个真实的安全面。 最低限度的防护是:用一个只读账号连数据库,并且限制它只能看到该看的那几张表。 如果错,会错在: 如果你的用法是只在一份离线拷贝的数据上跑查询、结果又有人工复核, 那②可以放松,但①依然在——它只能靠「把生成的查询语句也展示给用户」来缓解。

7. 怎么选:一张判据表

书里把三条路的取舍分散在三个地方,我们合成一张:

用户的问题类型该走哪条为什么走错了会怎样
查某一条记录(「64 号的学历?」)路一一行就是一块,直接检索得到
看整张表的样子(表很小)路二排版原样进提示,模型看得懂表一大就又慢又贵
加总、比例、排名、最大最小路三数据库真的算,不是抽样猜走路一或路二:得到一个基于少数行的错答案,且不报错

书里的三句结论原样对得上:小表转 Markdown 塞进提示; 大表把每一行拆成独立的一条来描述,适合「25 号赚没赚到 5 万」这类简单问题; 复杂问题(比如「本科学历里有多大比例年收入超过 5 万」)要用能在整个数据集上做加总的办法11

8. 直接从数据库拉数据

这一节换一个场景:数据本来就在数据库里,不是一份 Excel 文件。

作者的观察:我们的大部分数据都存在关系型数据库(就是把数据存成一张张有固定列的表、 表和表之间能互相引用的那种数据库,可以想成一堆被严格管起来的 Excel); PostgreSQL 是其中最流行的之一,网站、工具、机器都在往里写数据12

这个场景和前面三条路不是并列关系,先把这一层接上,否则下面两条做法没有位置。 数据本来就在数据库里,恰恰意味着路三(让模型写一句查询去算)是现成的—— 你连搬家都不用,表已经在那儿了。于是真正剩下的问题只有两个: 一是怎么把库连上,二是要不要顺便把嵌入也存进同一个库。 下面两条做法各管第一个问题的一半,再往下那一小节回答第二个问题。

书里给的两条真正影响判断的做法

书里这一节开了一串「装什么、用什么」的清单。其中只有两条会改变你的做法, 其余几条(装 PostgreSQL 时顺带装上的那个鼠标点点就能看数据的界面、 要同时管好几种数据库时换成别的管理工具、照某个教学网站灌一份练手数据) 是照做就行的安装步骤,列在脚注里13

第一条:连库用 SQLAlchemy,别为每种数据库各写一套取数代码。

书里称它是用得最多的 Python 数据库工具包14为什么这一条影响判断: 它在「你的取数代码」和「底下究竟是哪一种数据库」之间隔了一层—— 今天连 PostgreSQL,明天公司让你改连 MySQL,上层那些取数的代码基本不用动。 一套还没定型的 RAG 系统,换数据库这件事迟早会发生;这一层隔离值这个钱。

第二条:建一个专用的数据库用户,把账号密码写进 .env 文件15

.env 文件是什么: 一个不进代码仓库(团队用来存放代码、并且把每一次改动都记下来的 那个共享地方)的纯文本文件,专门放账号、密码、密钥这类东西,程序运行时从里面读。

为什么这一条影响判断: 密码只要写死在代码里,它就会跟着代码进代码仓库、进备份、 进每一个拿到这份代码的人手里——而且事后删不干净,代码仓库把每一次改动都记着。 「建一个专用用户」还顺带买到第二层好处:你可以只给它该有的那点权限, 这正好接上第 6 节那条「让模型生成的查询语句只能用只读账号去跑」。

这一节最值得带走的一句:一个数据库可以同时干两件事

书里提到 PostgreSQL 有个叫 pgvector 的扩展,装上之后 这个数据库既能存文字,也能存嵌入,还能直接做相似度搜索16

为什么这句话重要: 回想第 01 章那两条流水线—— 你本来需要「一个普通数据库存原文 + 一个向量库存嵌入」两套东西。 pgvector 让它们合成一套。

常见做法:PostgreSQL(存原文、存业务数据) + 另一个专门的向量库(存嵌入)
└─ 两套东西要同步,一致性靠你自己保证

pgvector:PostgreSQL 一套搞定
└─ 原文和嵌入一次写进去:要么两样都成,要么两样都不成

图说:少一个系统,就少一类「两边对不上」的故障。

那个「要么都成、要么都不成」的打包写入,数据库里有个专门的名字叫事务—— 好几个写入动作被捆成一件事,中途出岔子就整个撤销,不会留下写了一半的残局。 两套系统各写各的时候,你没有这样一个捆绑;一边写成了另一边挂了,就多出一条两边对不上的记录。

判断(我们的,不是书里的): 对绝大多数「几十万块以内」的内部知识库, 先用 pgvector,不要一上来就上专门的向量库。 理由不是它更快(它不更快),而是你少维护一个系统、少一类数据不一致的故障。 等到规模真的顶不住了再迁,那时候你已经知道自己的查询长什么样了。 如果错,会错在: 如果你的量级一开始就在千万级以上、或者需要专门的向量库才有的高级检索功能, 那么先上 pgvector 再迁移的成本会高于一步到位。判据是:预计块数,和你能接受的检索延迟。

9. 作者的判断与证据

说法属于哪一类
三种方式「根本不同」作者的框架,是这一节的组织方式,不是外部结论
大多数模型能读懂提示里的 Markdown 表格作者的经验,没给评测数据
路一只能用少数几行的信息机制上必然,不需要证据
「行改写成句子」也能用于分类有外部依据——书里指向了 TabLLM 那篇论文7
SQLAlchemy 是用得最多的工具包作者的说法,没给来源
PostgreSQL 加 pgvector 能一个数据库两用事实,可验证

注意:这一章唯一带外部依据的说法就是 TabLLM 那一条,而且是放在「另见」里的, 不是用来支撑正文论证的。

10. 边界与局限

  1. 书里没有讲怎么判断用户的问题属于哪一类。 三条路的选择被写成了一个设计期的决定,可现实里同一个用户会混着问三类问题。 一个真实系统需要一个前置的分诊步骤,书里没有;
  2. 没有讲混合方案。 比如同时建路一和路三,按问题类型分流——这是实际最常见的做法;
  3. Text-to-SQL 只有一段概念说明,没有示例代码。 这一节是全书唯一一个 「给了方向、没给做法」的配方;
  4. 示例代码里有一处对不上: 正文说查询结果会存进一个叫 results_df 的变量, 而代码里那个变量叫 result17。不致命,但说明这一版没人跑过。

11. 可带走的

  1. 表格不是文章——它的意思一半在格子里、一半在「哪个格子对哪个列头」里, 后者是抽成纯文字时最容易丢的;
  2. 三条路是三种保住对应关系的办法,不是三个可以叠加的技巧;
  3. 路一(每行改写成一句话): 把列头写进句子里;只能答「查某一条」;
  4. 路二(整表贴进提示): 转成 Markdown 格式模型才认;表一大就又慢又贵;
  5. 路三(让模型写查询语句): 唯一能正确回答加总类问题的路——算数交给数据库,措辞交给模型;
  6. 选路的依据不是你的数据,是你的用户会问什么;
  7. 走错路的代价是「安静的错答案」:系统照样给你一个百分比,不告诉你它只看了千分之一都不到的数据;
  8. 路三的两个代价书里没写:模型可能写出写法合规、逻辑却错的查询;以及让模型直接连生产数据库是个安全面—— 至少用只读账号;
  9. 「把表格行写成句子」有正经研究支撑(TabLLM),不只是权宜之计;
  10. .env 文件的唯一目的是别把密码写死在代码里;
  11. pgvector 让一个 PostgreSQL 同时当业务数据库和向量库用—— 中小规模优先选它,省掉一整类「两边对不上」的故障。

12. 原文地图

主题原书章原文位置
三种根本不同的方式Chapter 1. Loading Datatext/04-ch01-chapter-1-loading-data.txt:199(搜「three fundamentally different ways to handle Excel and CSV files」)
路一:每行打包成一段文字Chapter 1. Loading Datatext/04-ch01-chapter-1-loading-data.txt:201(搜「pack the information of each row in the dataset in a text snippet」) · text/04-ch01-chapter-1-loading-data.txt:211(搜「Think of it like explaining the data to a child who has never seen it before」)
路二:整表进提示、要用 MarkdownChapter 1. Loading Datatext/04-ch01-chapter-1-loading-data.txt:203(搜「you can load the whole table into the prompt」) · text/04-ch01-chapter-1-loading-data.txt:219(搜「Transform the table into markdown syntax」)
路三:Text-to-SQL、做加总Chapter 1. Loading Datatext/04-ch01-chapter-1-loading-data.txt:205(搜「use a text-to-SQL approach to answer user questions」) · text/04-ch01-chapter-1-loading-data.txt:229(搜「aggregations over the whole table」) · text/04-ch01-chapter-1-loading-data.txt:229(搜「The LLM will generate an SQL query」)
三条路的取舍Chapter 1. Loading Datatext/04-ch01-chapter-1-loading-data.txt:225(搜「Option 1 is straightforward to implement but can only answer questions using information from a few rows」) · text/04-ch01-chapter-1-loading-data.txt:225(搜「Option 2 can be expensive for large tables and struggles with complex queries」)
示例数据集与固定模板Chapter 1. Loading Datatext/04-ch01-chapter-1-loading-data.txt:239(搜「15 columns and 48,000 rows」) · text/04-ch01-chapter-1-loading-data.txt:245(搜「we are using a static template」)
模型是为文字设计的Chapter 1. Loading Datatext/04-ch01-chapter-1-loading-data.txt:273(搜「LLMs and embedding models are designed to work with text」)
三类问题的具体例子Chapter 1. Loading Datatext/04-ch01-chapter-1-loading-data.txt:275(搜「For small tables, convert them to Markdown format」) · text/04-ch01-chapter-1-loading-data.txt:277(搜「Is candidate 25 earning more than 50k」) · text/04-ch01-chapter-1-loading-data.txt:279(搜「What percentage of candidates with a Bachelor」)
TabLLM(另见)Chapter 1. Loading Datatext/04-ch01-chapter-1-loading-data.txt:283(搜「TabLLM: Few-shot Classification of Tabular Data」)
PostgreSQL 与工具Chapter 1. Loading Datatext/04-ch01-chapter-1-loading-data.txt:295(搜「install the PostgreSQL server and pgAdmin」) · text/04-ch01-chapter-1-loading-data.txt:297(搜「consider using DBeaver or DataGrip」) · text/04-ch01-chapter-1-loading-data.txt:299(搜「SQLalchemy is the most used Python SQL toolkit」)
凭据放 .envChapter 1. Loading Datatext/04-ch01-chapter-1-loading-data.txt:309(搜「Store the user credentials in a .env file」)
数据大多在关系型数据库里、pgvectorChapter 1. Loading Datatext/04-ch01-chapter-1-loading-data.txt:333(搜「Most of our data is stored in relational databases」) · text/04-ch01-chapter-1-loading-data.txt:335(搜「with the pgvector extension, it can also store vector embeddings」)
变量名对不上Chapter 1. Loading Datatext/04-ch01-chapter-1-loading-data.txt:329(搜「the retrieved data will be stored in the data frame results_df」)

Footnotes

  1. 出处:「Chapter 1. Loading Data」第 239 段(text/04-ch01-chapter-1-loading-data.txt:239,搜「15 columns and 48,000 rows」)与第 245 段(text/04-ch01-chapter-1-loading-data.txt:245,搜「we are using a static template」)。用的是一份公开的「人口收入」数据,常被拿来练二分类——就是让程序对每一行只给出两个答案之一,这里是「年收入超没超过 5 万」。 2

  2. 出处:「Chapter 1. Loading Data」第 199 段(text/04-ch01-chapter-1-loading-data.txt:199,搜「three fundamentally different ways to handle Excel and CSV files」)。

  3. 出处:「Chapter 1. Loading Data」第 273 段(text/04-ch01-chapter-1-loading-data.txt:273,搜「LLMs and embedding models are designed to work with text」)。原文把这句话当作整个配方的出发点:「RAG 系统面临的挑战是,大模型和嵌入模型都是为处理文字而设计的」。

  4. 出处:「Chapter 1. Loading Data」第 211 段(text/04-ch01-chapter-1-loading-data.txt:211,搜「Think of it like explaining the data to a child who has never seen it before」)。

  5. 出处:「Chapter 1. Loading Data」第 201 段(text/04-ch01-chapter-1-loading-data.txt:201,搜「pack the information of each row in the dataset in a text snippet」)。原文说这样一来就可以「像处理纯文本文档那样加载这份数据集」。

  6. 出处:「Chapter 1. Loading Data」第 225 段(text/04-ch01-chapter-1-loading-data.txt:225,搜「Option 1 is straightforward to implement but can only answer questions using information from a few rows」)与同段(text/04-ch01-chapter-1-loading-data.txt:225,搜「Option 2 can be expensive for large tables and struggles with complex queries」)。 2

  7. 出处:「Chapter 1. Loading Data」第 283 段(text/04-ch01-chapter-1-loading-data.txt:283,搜「TabLLM: Few-shot Classification of Tabular Data」)——书里只给了论文标题,没有展开。补充(不在书里):这篇论文首次提交于 2022 年 10 月 19 日,作者为 Stefan Hegselmann、Alejandro Buendia、Hunter Lang 等;做法是把表格的一行摊平成一句自然语言、配上一段任务说明去提示大模型,摘要称这一做法「在样本极少的设定下,足以与梯度提升树这类强传统基线竞争」——梯度提升树是处理表格数据的一种传统看家算法,「基线」指的是拿来当参照的现有最好办法;摘要还说,一个样例都不给(行话叫零样本)也能有像样的表现。来源:《TabLLM: Few-shot Classification of Tabular Data with Large Language Models》https://arxiv.org/abs/2210.10723(查阅于 2026-08-25)。 2 3

  8. 出处:「Chapter 1. Loading Data」第 203 段(text/04-ch01-chapter-1-loading-data.txt:203,搜「you can load the whole table into the prompt」)与第 219 段(text/04-ch01-chapter-1-loading-data.txt:219,搜「Transform the table into markdown syntax」)。

  9. 出处:「Chapter 1. Loading Data」第 205 段(text/04-ch01-chapter-1-loading-data.txt:205,搜「use a text-to-SQL approach to answer user questions」)与第 229 段(text/04-ch01-chapter-1-loading-data.txt:229,搜「aggregations over the whole table」与「The LLM will generate an SQL query」)。

  10. 出处:「Chapter 1. Loading Data」第 279 段(text/04-ch01-chapter-1-loading-data.txt:279,搜「Libraries like Vanna AI make this straightforward」)。我们没有核对过这个库今天的状态,书里也只是一句提及。

  11. 出处:「Chapter 1. Loading Data」第 275 段(text/04-ch01-chapter-1-loading-data.txt:275,搜「For small tables, convert them to Markdown format」)、第 277 段(text/04-ch01-chapter-1-loading-data.txt:277,搜「Is candidate 25 earning more than 50k」)与第 279 段(text/04-ch01-chapter-1-loading-data.txt:279,搜「What percentage of candidates with a Bachelor」)。

  12. 出处:「Chapter 1. Loading Data」第 333 段(text/04-ch01-chapter-1-loading-data.txt:333,搜「Most of our data is stored in relational databases」)。

  13. 出处:装 PostgreSQL 时顺带装上图形界面见「Chapter 1. Loading Data」第 295 段(text/04-ch01-chapter-1-loading-data.txt:295,搜「install the PostgreSQL server and pgAdmin」);要同时管好几种数据库时换别的管理工具见第 297 段(text/04-ch01-chapter-1-loading-data.txt:297,搜「consider using DBeaver or DataGrip」),原文点名的两个都支持 MySQL、SQLite、Oracle 等多种;练手数据见第 305 段(text/04-ch01-chapter-1-loading-data.txt:305,搜「replicate the sample database from w3schools.com」)。这三条都是照做就行的安装步骤,选哪个不影响你的系统怎么搭。

  14. 出处:「Chapter 1. Loading Data」第 299 段(text/04-ch01-chapter-1-loading-data.txt:299,搜「SQLalchemy is the most used Python SQL toolkit」)。原书这里把库名写成了 SQLalchemy,正式写法是 SQLAlchemy。「换数据库时上层代码不用动」这一步推论是我们补的,不是原文。

  15. 出处:「Chapter 1. Loading Data」第 309 段(text/04-ch01-chapter-1-loading-data.txt:309,搜「Store the user credentials in a .env file」)。原文的步骤是:建用户、按官方文档授权、把凭据写进 .env。原文没有说明为什么要这么做,「密码进了代码仓库就删不干净」「专用用户可以只给该有的权限」这两条理由是我们补的。

  16. 出处:「Chapter 1. Loading Data」第 335 段(text/04-ch01-chapter-1-loading-data.txt:335,搜「with the pgvector extension, it can also store vector embeddings」)。原文紧接着又是一个指向不存在章节的占位符。

  17. 出处:「Chapter 1. Loading Data」第 329 段(text/04-ch01-chapter-1-loading-data.txt:329,搜「the retrieved data will be stored in the data frame results_df」)。示例代码里那个变量实际叫 result