Text2Sql多表联查

摘要:线上 MySQL 生产环境几乎不再使用物理外键约束,依靠字段注释、文档维护逻辑关联是行业主流。本文基于 Chat2DB 社区版 5.3.8,搭建一套无物理外键的电商测试数据集,验证 Chat2DB 原生自带的 @表名 唤起表语法,测试工具在仅依靠注释、逻辑关联描述的前提下,自动识别表关联、一对多聚合、退款口径过滤的能力,同时复盘自然语言需求歧义带来的边界问题。

1、背景:为什么生产不用物理外键?

传统教学场景写 SQL 时习惯增加 FOREIGN KEY 物理外键,但在互联网业务、大数据数仓场景,物理外键有明显短板:

  1. DML 操作会触发外键校验,增加行锁,高并发下容易产生锁等待;
  2. 分库分表、数据迁移、批量数据导入时,外键约束会阻碍数据写入;
  3. 业务变更、数据归档、软删除场景,外键约束维护成本极高;

所以目前主流方案:仅保留主键,通过字段注释、业务文档维护表与表之间的逻辑关联关系 ,也就是逻辑外键。 而 Text2SQL 工具(Chat2DB)想要在生产落地,核心考验就是:能不能不靠数据库元数据的物理外键,仅依靠注释、表关系描述,自动完成正确的多表 JOIN。

2、测试数据集设计

我们搭建一套电商订单场景,包含会员、商品、订单主表、订单明细、售后退款 5 张表,所有关联依靠注释描述,无任何外键约束。

2.1 DDL 建表语句

复制代码
-- 会员表 member
CREATE TABLE member (
    member_id BIGINT PRIMARY KEY COMMENT '会员ID【逻辑主键】',
    member_name VARCHAR(50) NOT NULL COMMENT '会员名称',
    member_level TINYINT COMMENT '会员等级 0普通 1银卡 2金卡 3钻石',
    register_time DATETIME COMMENT '注册时间',
    phone VARCHAR(11) COMMENT '手机号'
) ENGINE=InnoDB COMMENT='会员基础表';

-- 商品表 goods
CREATE TABLE goods (
    goods_id BIGINT PRIMARY KEY COMMENT '商品ID【逻辑主键】',
    goods_name VARCHAR(100) NOT NULL COMMENT '商品名称',
    category_id INT COMMENT '分类ID',
    sale_price DECIMAL(10,2) COMMENT '售卖单价',
    cost_price DECIMAL(10,2) COMMENT '成本单价',
    stock_num INT COMMENT '当前库存数量'
) ENGINE=InnoDB COMMENT='商品基础表';

-- 订单主表 order_main
CREATE TABLE order_main (
    order_id BIGINT PRIMARY KEY COMMENT '订单号【逻辑主键】',
    member_id BIGINT COMMENT '会员ID,逻辑关联 member.member_id',
    order_time DATETIME COMMENT '下单时间',
    pay_time DATETIME COMMENT '支付时间',
    total_amount DECIMAL(12,2) COMMENT '订单实付总金额',
    order_status TINYINT COMMENT '订单状态 0待支付 1已支付 2已发货 3已完成 4已取消',
    warehouse_id INT COMMENT '发货仓库ID'
) ENGINE=InnoDB COMMENT='订单主表,一个会员多条订单';

-- 订单明细表 order_item
CREATE TABLE order_item (
    item_id BIGINT PRIMARY KEY COMMENT '明细ID【逻辑主键】',
    order_id BIGINT COMMENT '订单ID,逻辑关联 order_main.order_id',
    goods_id BIGINT COMMENT '商品ID,逻辑关联 goods.goods_id',
    buy_num INT COMMENT '购买数量',
    item_price DECIMAL(10,2) COMMENT '下单快照单价'
) ENGINE=InnoDB COMMENT='订单明细,一条订单多条明细,一条明细对应一个商品';

-- 售后退款表 refund_record
CREATE TABLE refund_record (
    refund_id BIGINT PRIMARY KEY COMMENT '退款记录ID【逻辑主键】',
    order_id BIGINT COMMENT '订单ID,逻辑关联 order_main.order_id',
    refund_amount DECIMAL(12,2) COMMENT '本次退款金额',
    refund_time DATETIME COMMENT '退款时间',
    refund_status TINYINT COMMENT '0待审核 1退款成功 2驳回'
) ENGINE=InnoDB COMMENT='售后退款表,一个订单可以多次发起退款';
  • 逻辑外键:必须在注释上写清楚关联关系,如:'会员ID,逻辑关联 member.member_id'。

2.2 逻辑关联说明

逻辑关联关系:

  • member.member_id <--> order_main.member_id:一对多,1个会员可以有多条订单
  • order_main.order_id <--> order_item.order_id:一对多,1个订单对应多条商品明细
  • goods.goods_id <--> order_item.goods_id:一对多,一个商品可以出现在多条订单明细
  • order_main.order_id <--> refund_record.order_id:一对多,一个订单支持多次退款记录

所有关联均为逻辑关联,无数据库物理外键。

2.3 测试数据

数据设计思路:制造一个订单多条退款、退款有驳回状态、待支付订单、已取消订单等边界场景,专门用来检验一对多联表求和时会不会出现金额膨胀。

复制代码
INSERT INTO member(member_id,member_name,member_level,register_time,phone) VALUES
(1001,'张三',3,'2025-03-12 09:20:00','13800001001'),
(1002,'李四',2,'2025-05-20 14:10:00','13800001002'),
(1003,'王五',1,'2026-01-08 10:30:00','13800001003'),
(1004,'赵六',0,'2026-06-15 16:40:00','13800001004'),
(1005,'孙七',3,'2025-11-02 11:00:00','13800001005');

INSERT INTO goods(goods_id,goods_name,category_id,sale_price,cost_price,stock_num) VALUES
(2001,'机械键盘',101,299.00,120.00,120),
(2002,'无线鼠标',101,89.00,35.00,300),
(2003,'27寸显示器',102,1299.00,580.00,45),
(2004,'笔记本支架',103,49.00,18.00,500),
(2005,'降噪耳机',104,499.00,210.00,88);

INSERT INTO order_main(order_id,member_id,order_time,pay_time,total_amount,order_status,warehouse_id) VALUES
(30001,1001,'2026-10-02 08:30:00','2026-10-02 08:32:00',1598.00,3,1),
(30002,1001,'2026-10-05 15:10:00','2026-10-05 15:15:00',299.00,2,1),
(30003,1002,'2026-10-08 09:05:00','2026-10-08 09:07:00',588.00,3,2),
(30004,1002,'2026-10-10 11:20:00',NULL,499.00,0,2),
(30005,1003,'2026-10-12 16:00:00','2026-10-12 16:03:00',1348.00,3,1),
(30006,1004,'2026-10-15 10:40:00','2026-10-15 10:42:00',49.00,4,3),
(30007,1005,'2026-10-18 14:25:00','2026-10-18 14:28:00',1798.00,3,2),
(30008,1005,'2026-10-22 17:10:00','2026-10-22 17:12:00',89.00,1,1);

INSERT INTO order_item(item_id,order_id,goods_id,buy_num,item_price) VALUES
(40001,30001,2003,1,1299.00),
(40002,30001,2002,2,89.00),
(40003,30002,2001,1,299.00),
(40004,30003,2001,1,299.00),
(40005,30003,2002,1,89.00),
(40006,30003,2004,4,49.00),
(40007,30005,2003,1,1299.00),
(40008,30005,2004,1,49.00),
(40009,30006,2004,1,49.00),
(40010,30007,2003,1,1299.00),
(40011,30007,2005,1,499.00),
(40012,30008,2002,1,89.00);

INSERT INTO refund_record(refund_id,order_id,refund_amount,refund_time,refund_status) VALUES
(50001,30001,200.00,'2026-10-04 10:00:00',1),
(50002,30001,150.00,'2026-10-04 10:05:00',2),
(50003,30007,300.00,'2026-10-20 09:30:00',1),
(50004,30007,260.00,'2026-10-21 11:15:00',1);

3、AI提问生成sql

Chat2DB 输入框内置原生 @ 能力:在对话输入框输入 @,会弹出数据表下拉选择面板,选中后自动带入本次查询上下文,限定本次查询只使用标记的表。

3.1 提问内容

复制代码
统计2026-10-01到2026-10-24,每个会员的:下单订单总数、订单总实付金额、成功退款总金额、净营收(实付总额-成功退款总额)
用到表:member order_main  refund_record

3.2 回复结果

sql

复制代码
SELECT
  m.member_id,
  m.member_name,
  COUNT(DISTINCT o.order_id)                                     AS order_cnt,
  IFNULL(SUM(o.total_amount), 0)                                 AS pay_amount,
  IFNULL(SUM(r.refund_amt), 0)                                   AS refund_amount,
  IFNULL(SUM(o.total_amount), 0) - IFNULL(SUM(r.refund_amt), 0)  AS net_revenue
FROM member m
LEFT JOIN (
    SELECT order_id, member_id, total_amount
    FROM order_main
    WHERE order_time >= '2026-10-01 00:00:00'
      AND order_time <  '2026-10-25 00:00:00'
) o ON o.member_id = m.member_id
LEFT JOIN (
    SELECT order_id, SUM(refund_amount) AS refund_amt
    FROM refund_record
    WHERE refund_status = 1
    GROUP BY order_id
) r ON r.order_id = o.order_id
GROUP BY m.member_id, m.member_name
ORDER BY m.member_id;
  • 核心亮点:自动识别一对多风险,先对子表 refund_record 预聚合求和,再关联订单主表,规避金额重复膨胀经典坑。

4、验证结果复盘

  • 严格恪守字面需求:优先输出和提问完全匹配的 SQL 与结果,不擅自修改用户口径;
  • 具备业务查漏补缺能力:识别自然语言里的歧义点,主动补充行业最常用的另一套口径对照表;
  • 一对多陷阱识别到位:退款表先聚合再 JOIN,规避求和膨胀,是 Text2SQL 最容易翻车的点;
  • 细节规范:左闭右开时间区间、LEFT JOIN 保维度不丢行、自动过滤驳回退款;
  • 可审计:两套结果、差异原因、边界说明全部写清楚,使用者自己选择符合业务的口径,而不是模型替用户做决定。

5、落地最佳实践(Chat2DB + DeepSeek + 逻辑外键)

  1. 优先使用@指定数据表:逻辑外键场景没有数据库外键元数据,缩小表范围是提升准确率的第一手段;
  2. 规范注释 :关联字段写明逻辑关联 xx表.xx字段,提前整理一对多 / 一对一关系;
  3. 一对多子表强制预聚合:类似退款、明细这类子表,必须先 GROUP BY 聚合再 JOIN;DeepSeek 识别很好,复杂场景也可以在 prompt 主动强调;
  4. 善用双口径提示:当模型输出口径对照时,确认业务口径,把明确状态过滤条件补充到下一次提问;
相关推荐
xixingzhe23 天前
Text2Sql:Chat2DB使用DeepSeek
text2sql
xiancai_xianyu12 天前
Text2SQL落地生产库前,SQL要过哪三道闸
前端·数据库·sql·sql注入·text2sql·ai agent开发·企业级ai框架
只是甲1 个月前
Text2SQL 系列博客 07:4 步选型法(上)- 场景与标准的定义
人工智能·text2sql·nl2sql·ai agent·智能问数
只是甲1 个月前
Text2SQL 系列博客 08:4 步选型法(下)- 评估打分与最终决策
text2sql·nl2sql·ai agent·智能问数
只是甲1 个月前
Text2SQL 系列博客 04:SuperSonic 深度剖析 - 腾讯音乐开源,把 Headless BI + Chat BI 玩明白了
text2sql·ai agent·智能问数·supersonic·开源text2sql
黄骨鱼骨2 个月前
DatI:给AI接入数据库,Agent时代的NL2SQL问数与多维表格应该怎么做
java·database·text2sql·nl2sql·chatbi·mcp·data agent
xiancai_xianyu2 个月前
企业智能问数怎么落地,从自然语言到SQL那道坎有多难
数据库·人工智能·sql·text2sql·企业智能问数·agentrag·ai推理可视化
smilejingwei3 个月前
Text2SQL 想落地,要在承认 AI 有幻觉的前提下设计方案
text2sql·智能问数·润乾nlq
只是甲3 个月前
Text2SQL 系列博客 02:技术原理深度剖析 - 从自然语言到 SQL 的完整链路
人工智能·text2sql·nl2sql·ai agent·自助数据分析·data agent·agentic 数据洞察