ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

AI写SQL实战:喂对表结构和业务口径,生成结果才能直接跑

AI写SQL实战:喂对表结构和业务口径,生成结果才能直接跑 写 SQL 这个活儿说难不难说简单也不简单。业务侧问问题通常是这样“月底了帮我看看哪些城市的用户最近一个月没下过单”或者“统计一下上周各品类的退款率”。翻译成 SQL 倒不是不会但每次都要手动拼表名、对齐字段口径、考虑要不要加 group by不仅慢而且容易在上线的 SQL 里埋雷。后来我养成了一个新习惯把这类需求先丢给 AI 转换一遍再用我自己的经验去校验和兜底。这篇文章就是围绕“AI 应用之使用 AI 转换 SQL 语句”这个主题把我在实际项目中怎么用 AI 写 SQL、怎么把准确性从“大概能用”提升到“可以直接跑”的经验拿出来分享。适合刚接触 SQL或者每天忙着写报表、查数、做数据分析的工程师和运营同学也适合想让 AI 真正落地到工作流里的团队参考。1. 整体思路拆解AI 转换 SQL 到底值不值得用1.1 把“手写”变成“提示-生成-审核”我在刚开始尝试 AI 写 SQL 时周围同事的反应分两类一类觉得这玩意就是玩具生成的 SQL 跑起来全是坑另一类直接把它当成全自动工具生成的语句复制到生产库就跑。两边我都待过最后得出的结论是不要把 AI 当成“全自动程序员”要把它当成一个“需要你把需求讲得明明白白的同事”。过去我们写出一段复杂 SQL 的流程大概是接到需求、在脑子里把业务订单翻译成表关系、翻查字段字典、设计 SQL 骨架再逐句调整。现在我的流程变成了接到需求、把业务口径写清楚、把表结构信息贴给 AI、让 AI 给出 SQL 初稿、我负责审和执行。这个流程变化的核心并不是省掉 SQL 语法学习而是把大量的“查表名、查字段、想 join 怎么写”这类型工作转移给 AI人只负责判断“合不合理”。使用 AI 生成 SQL 的底层逻辑是自然语言和结构化查询之间存在一条清晰的映射路径这个映射虽然琐碎但一旦说清楚规则就非常固定。AI 模型在几十万条“问题-表结构-SQL 答案”上训练过它对常见 join、group by、窗口函数的掌握其实比平均水平的人类工程师还要熟练。它最大的短板不是语法而是不了解你的库结构、你的业务口径以及你所在公司的具体规则。所以能不能用好它关键就看你怎么喂上下文。1.2 收益最大的 3 类场景第一类是临时取数和报表分析。业务同学跑过来问“最近七天注册用户里有多少是安卓端”你不必每次都自己敲 SQL直接把需求扔给 AI基于你提供的数据字典和表结构一分钟内就能拿到初稿。这种高频、低风险、可复核的场景省时效果最明显。第二类是数据分析师写复杂报表。比如需要计算同比环比、每个渠道的留存率、分层分群的用户行为路径这些 SQL 往往结构复杂一条语句里既有子查询又有窗口函数。AI 能快速给出结构参考你只需要调整口径比从零开始写要省很多时间。第三类是学习 SQL 的新人。让 AI 先写一段 SQL然后你再对照执行计划和最终结果去理解“为什么用 LEFT JOIN、为什么这里要 GROUP BY”比看教程更直观。当然这里有个前提你得有辨别能力不能 AI 给什么就信什么。1.3 不建议用 AI 生成 SQL 的 4 类场景不要用在生产库的 DDL 上。创建表、修改表结构、加索引这类操作影响面是整个服务AI 生成完你也不可能百分百信任不如干脆手写。不要用在大批量 UPDATE/DELETE 上。AI 很容易在条件判断上出偏差比如少一个 WHERE 条件或者把状态字段写反一旦跑到生产库就是事故。不要用在与资金相关的核心对账上。涉及金额分摊、流水匹配、汇率换算这类有严格业务规则的地方需要数据可追溯、规则可解释AI 生成的结果很难做到这种程度的确定性。不要用在你完全不熟悉的业务表上。UI 上看着字段叫flag你都不知道它存储的是哪几位状态码AI 也不可能知道。这种情况即使生成了 SQL也只是看起来像那么回事执行结果基本无法验证。1.4 大模型选型通用模型和专门工具怎么配我日常使用到的方案主要分三类在线大模型 API、本地部署模型、数据库厂商自带的 AI 助手。这三类各有侧重。如果是临时查数、字段不复杂、对数据保密性要求不高的场景我常用 DeepSeek、通义千问这类中文能力强的模型它们对中文口语化需求的理解很到位生成的 SQL 风格也偏简洁。如果是特别复杂的分析查询比如多层嵌套的窗口函数、递归 CTE我会用 GPT-4 或者 Claude 这类在代码生成上更强的模型它们在逻辑链路长的场景下表现得明显更稳。有些公司对数据安全非常敏感不允许把表结构传到公网那就用本地部署方案比如通过 Ollama 跑 Qwen 系列或者 Llama 系列。但本地的小参数模型在“复杂 SQL 生成”这个任务上会明显降智字段一多就容易编造表名所以需要配合严格的提示词模板来使用。数据库厂商自带的 AI 助手通常集成了它自己库引擎的方言和权限体系比如能自动按当前账号的权限约束生成语句这类工具胜在融合度但灵活性有时候反而不如通用模型。方案优点缺点适合场景在线大模型 API理解能力强、生成质量稳定存在表结构外泄风险内部测试、非敏感数据本地开源模型数据不出内网、可控性好小参数模型质量不稳定数据保密要求高的公司数据库厂商 AI 助手天然适配方言、权限可控能力边界受厂商限制有统一数据平台的企业2. 核心细节怎样给 AI 喂上下文才能稳定生成正确 SQL2.1 表结构信息永远是第一优先级想让 AI 转换 SQL 准确第一原则把表结构给它而不是只给一句“帮我写个 SQL”。很多人说“AI 生成的 SQL 老假想表名”十有八九是没把表的元信息喂进去。模型在缺少上下文的时候会下意识从训练记忆里“编”一个最像的字段名出来这个行为本质上是概率补全不是逻辑推导。所以我在每次请求里都会把相关字段的建表语句直接贴进去。比如CREATE TABLE customers ( id BIGINT PRIMARY KEY, name VARCHAR(50), city VARCHAR(50), registered_at DATETIME, last_login_at DATETIME ); CREATE TABLE products ( id BIGINT PRIMARY KEY, name VARCHAR(100), category VARCHAR(50), price DECIMAL(10,2) ); CREATE TABLE orders ( id BIGINT PRIMARY KEY, customer_id BIGINT, product_id BIGINT, order_status VARCHAR(20), amount DECIMAL(10,2), created_at DATETIME );注意这里我故意没有贴太多无关字段只贴了当前需求会用到的表。如果库里有 50 张表你全贴进去模型容易被干扰反而会 join 出多余的表。我的经验是每次对话只喂当前查询涉及的那几张表。数据库字段多的时候先用DESC table;查一下结构再复制而不是凭记忆写。还有一个细节字段名如果包含业务缩写最好在注释里说明。比如order_status的取值范围是pending/paid/refunded/closed你在建表语句后面加一句备注“状态字段paid 表示已支付”AI 生成的查询条件就大概率不会用错枚举值。2.2 业务口径要写进提示词SQL 转换的难点不在语法在自然语言到业务口径的映射。比如“有效客户”不同业务团队理解完全不同有人觉得注册满七天算有效有人觉得下单算有效还有人觉得必须有实名认证。你如果不把口径写清楚AI 只能凭直觉猜猜错就是返工。我的做法是把口径作为一个独立段落放在表结构之后。比如有效客户注册时间超过 7 天且 last_login_at 在最近 90 天内的用户。订单金额指 orders.amount 字段实付金额不包含已退款订单。时间范围默认使用东八区按自然日计算。这些信息看起来琐碎但 AI 生成 SQL 时判断条件怎么写、where 怎么加本质上全靠这些上下文。所谓“把需求讲明白”不是把中文需求复制粘贴一遍而是把需求里每个含糊的词都翻译成明确的筛选条件。我踩过的一个典型坑是“最近7天”。有的模型会把条件写成created_at NOW() - INTERVAL 7 DAY有的写成created_at CURDATE() - INTERVAL 7 DAY。前者按当前时刻往前推 7×24 小时后者按自然日从零点开始算。如果业务方要的是自然日你就必须在提示词里写清楚“按自然日统计使用 CURDATE()”。这个差异在数据量大的报表里能差出不少行口径不一样结果自然对不上。2.3 利用输出约束减少安全风险和返工生成 SQL 的时候AI 很容易在输出格式上“自由发挥”有时候带一句解释有时候把 SQL 和自然语言混在一起有时候“好心”地给你加一段UPDATE或者DELETE示例。如果这些内容被直接复制进 IDE 或者数据库客户端轻则语法报错重则出现误操作。所以我在提示词里会加三行硬约束只输出 SELECT 查询禁止生成 INSERT、UPDATE、DELETE、DROP 等写操作语句。用代码块包裹 SQL行内不要混入自然语言解释。每个涉及时间筛选的条件必须用中文注释标明口径方便我复核。加完这些约束之后AI 的输出规范性会好很多基本不会出现“顺带科普”的长篇大论生成的 SQL 也更适合直接落到编辑器里继续改。安全层面你可以把“禁止 DML/DDL”这句话当作一个兜底防线但后面讲团队接入的时候我还会给数据库账号加一层只读权限双保险才靠谱。2.4 一个可以抄作业的提示词模板我现在给团队内部整理了一套固定模板每个分析师在使用 AI 生成 SQL 前都先套这套模板效果比自由发挥稳定不少请基于我提供的表结构把下面的业务需求转换成 SQL 查询。 【表结构】 在此粘贴 CREATE TABLE 语句 【业务口径】 - 订单金额定义为 orders.amount仅统计 order_status paid 的订单。 - 时间默认使用东八区所有日期范围按自然日计算。 - 客户 city 为空时不参与城市维度统计。 【输出要求】 1. 只输出 SELECT 查询禁止生成 INSERT、UPDATE、DELETE、DROP 等语句。 2. 结果用 markdown 代码块包裹不要夹杂自然语言解释。 3. 字段名和表名必须来自我提供的表结构不允许编造。 4. 如涉及多步计算优先使用 CTE 分段书写每段加中文注释。 5. 不要额外加 LIMIT除非我在需求里明确要求。 【业务需求】 在此粘贴你的中文需求这个模板不是万能的但它把“上下文损耗”降到了最低。你一旦把表结构、口径、输出要求三段都填好AI 生成的 SQL 质量基本能到七八十分剩下的二三四十分就是你要审的部分。3. 实操实录从中文需求到可直接执行的 SQL 查询3.1 准备测试表和示例数据纸上谈兵没有意思我用一个真实的电商场景来走一遍完整流程。假设数据库里有三张表customers客户、products商品、orders订单这是最常见的关系型结构。建表语句就用上一个小节提过的那三张我再补一条外键关系说明orders.customer_id关联customers.idorders.product_id关联products.id。业务方现在提了一个新需求“帮我看看最近 30 天销售额排名前 5 的商品带出商品名称、分类和销售额。”这个需求听起来简单但里面有三个地方需要确认销售额按什么金额算是按下单时间还是支付时间“最近30天”按自然日还是按当前时刻往前推我把这些问题整理成口径说明和建表语句一起放进提示词。3.2 简单聚合查询近30天销售额 Top5 商品在提示词里填好内容后AI 给出的初始 SQL 通常是下面这个样子SELECT p.id, p.name, p.category, SUM(o.amount) AS sales_amount FROM orders o JOIN products p ON o.product_id p.id WHERE o.order_status paid AND o.created_at CURDATE() - INTERVAL 30 DAY GROUP BY p.id, p.name, p.category ORDER BY sales_amount DESC LIMIT 5;这段 SQL 逻辑上没问题join 关系正确条件筛选也符合我们设定好的口径。但我审的时候会多看一眼LIMIT 5是我在模板里没有要求的AI 凭借“前5”这个词自动加上了。这里没有大问题但如果你把“前5”理解为业务希望把 Top5 商品完整列出这个LIMIT 5就是对的不需要改。真正要注意的是sales_amount的统计维度。订单表的amount是订单实付金额但一个订单如果包含多个商品它的amount会被计入每一个商品行这样 SUM 之后其实并不精确。更严谨的做法是订单明细表拆开而这里我们是简化场景只有一张订单表且每次订单对应一个商品所以结果可用。这说明什么呢说明 AI 只能基于你给的表结构做推理表结构本身如果粒度不对AI 生成的 SQL 再好也救不了结果。你需要自己意识到数据模型的粒度问题这也是人机协作里人的价值。3.3 复杂口径查询分城市统计复购用户数第二个需求升级一下“统计最近 30 天每个城市的下单人数和复购人数复购指下了不止一单。”这个 SQL 涉及子查询和 CASE WHEN对新手来说容易绕晕。把需求写进提示词后AI 给我的是SELECT c.city, COUNT(DISTINCT c.id) AS buyers, COUNT(DISTINCT CASE WHEN t.order_count 2 THEN c.id END) AS repeat_buyers FROM customers c JOIN ( SELECT customer_id, COUNT(*) AS order_count FROM orders WHERE order_status paid AND created_at NOW() - INTERVAL 30 DAY GROUP BY customer_id ) t ON c.id t.customer_id GROUP BY c.city;这段 SQL 的第一眼看没问题但它踩了一个隐蔽的语义坑子查询里的NOW() - INTERVAL 30 DAY是按“当前时刻往前推 30×24 小时”计算不是按自然日。如果我们的口径要求是“从今天零点往回数 30 天”这里就会在第一个和最后一个整天上稍微偏一点。对于快照类报表这个偏差可能没人发现但对于严格的对账场景这就是错误。我调整后的版本是这样的SELECT c.city, COUNT(DISTINCT c.id) AS buyers, COUNT(DISTINCT CASE WHEN t.order_count 2 THEN c.id END) AS repeat_buyers FROM customers c JOIN ( SELECT customer_id, COUNT(*) AS order_count FROM orders WHERE order_status paid AND created_at CURDATE() - INTERVAL 30 DAY GROUP BY customer_id ) t ON c.id t.customer_id GROUP BY c.city;这里还有一个细节CASE WHEN t.order_count 2 THEN c.id END放在COUNT(DISTINCT ...)里面可以正确统计“复购人数”不会因为一个人下三单就重复计算。这个写法 AI 生成得不错但也确实有模型在这种场景会直接COUNT(CASE ...)漏掉 DISTINCT导致结果偏大。你在审的时候看到这种去重计算一定要格外留神。3.4 用执行计划验证 AI 生成的 SQLSQL 写得对不对最终还要看能不能稳定、快速地在真实数据上跑出来。我在正式跑之前都会先给 SQL 前面加一个EXPLAIN看一眼执行计划。比如前面这条复购统计的 SQL如果orders表数据量过百万但没有order_status和created_at的联合索引执行计划大概率是全表扫描跑起来会很慢。我看到执行计划里出现type: ALL或者rows: 1000000这种字眼就会先考虑加索引。这里要强调AI 生成 SQL 时并不知道你的索引分布它只会按照逻辑正确性来写不会主动把“能否走索引”考虑进去。所以性能优化这件事必须由你来做。验证步骤我一般分三步先EXPLAIN看是否全表扫描再抽取一天数据跑子集看结果是否和预期一致最后才放开全量跑。如果 AI 生成的 SQL 里用了LEFT JOIN我还会特别检查一下因为LEFT JOIN在输出行数上和INNER JOIN不同一旦关联条件写漏很容易出现重复行。这类问题执行计划看不出来只能靠结果复核。4. 高频报错与排查技巧实录4.1 字段名、表名是 AI 编的这是新手最容易遇到的问题。明明表里没有user_name这个字段AI 偏给你写出一个user_name跑起来直接报Unknown column。原因很简单你没有把表结构喂进去或者喂的表结构和实际环境不一致AI 只能从训练数据里“回忆起”一个最像的字段名。解决手段是三层第一层提示词里明确写“字段名和表名必须来自我提供的表结构”第二层贴进去的建表语句一定是从数据库执行SHOW CREATE TABLE拿到的不是凭记忆写的避免你记错字段名第三层每次运行前先在库里执行一遍DESC核对字段。这三步做下来编造字段的问题基本能根除。4.2 SQL 逻辑对但性能差我见过最典型的场景是AI 生成了条件带有WHERE YEAR(created_at) 2024逻辑上没错但它在created_at上套了函数导致索引失效百万级数据全表扫描。AI 生成这种 SQL 的频率并不低因为它在训练数据里见过太多次这样的写法但它不具备“这个库里有没有索引”的感知能力。我自己习惯在提示词的输出要求里加一条“涉及日期条件时尽量使用日期范围比较避免在索引列上使用函数。”如果你用的是 2.4 节那个模板也可以把这条直接写进固定里。这样 AI 生成时就会倾向于写成created_at 2024-01-01 AND created_at 2025-01-01执行效率会好很多。另外AI 还特别喜欢在不需要的场景里用DISTINCT。它看到两表关联怕产生重复行下意识加一个DISTINCT结果就是所有字段都要参与去重内存开销翻倍。我的习惯是看到 AI 生成的 SQL 里有DISTINCT就先问自己一句这个重复行真的存在吗如果关联键是唯一的DISTINCT其实没必要去掉之后性能提升明显。4.3 同一个中文需求两次生成的语义不一致这是模型输出的随机性带来的问题。你把同一句“最近30天”发给同一个模型两次第一次给你NOW() - INTERVAL 30 DAY第二次可能给你CURDATE() - INTERVAL 30 DAY。模型本身没有上下文记忆每次都是独立预测所以输出不稳定。应对思路有两个。一个是靠模板把口径中那些容易有歧义的条件全部显式化比如直接写“按自然日计算使用 CURDATE()”模型就没有发挥空间了。另一个是固定温度参数如果你在用 API把 temperature 调到 0 或接近 0能让输出更确定不那么发散。团队内部如果有多个人都要用 AI 写 SQL我建议把提示词做成一个公用模板存到知识库大家统一用同一个版本这样输出风格和口径才容易对齐。拨打你个有意思的现象越“口语化”的问题模型理解偏差越大。比如“把这个月活跃用户数拉出来”“这个月”到底是自然月还是最近三十天模型只能靠猜。让 AI 猜业务口径本身就是在制造脏数据。宁可多写一句“本月指自然月从当月1号零点开始”也别嫌麻烦。4.4 复杂查询报错难定位时怎么办AI 生成的 SQL 如果是一条特别长的 CTE跑起来报错错误信息往往只指向某一个子查询肉眼找起来相当痛苦。我的做法是“拆段验证”从 AI 生成的 SQL 里挑出最后一段先单独跑确认这段没问题后再往前倒推一段逐段定位。这个方法适合所有复杂 SQL 的排查不只是 AI 生成的。还有一种情况是 SQL 本身不报错但结果明显不对比如关联之后行数翻倍。这种问题我会拿一小段时间窗口的数据手动数一下业务期望的行数再和 SQL 输出对比。比如原本 100 个用户查询结果出来 160 行那基本可以断定 join 产生了重复行。此时重点检查 join 条件里是不是漏了业务上的唯一键或者过滤条件是不是放在了ON而不是WHERE后面。AI 生成的语句里这类“逻辑没报错但结果错”的坑比语法错误更难发现一定要有“拿小样本手算一遍”的复核意识。高频问题现象排查方向预防手段编造字段Unknown column 报错核对表结构直接贴 SHOW CREATE TABLE 结果索引失效查询缓慢EXPLAIN 看 type 字段提示词禁止在索引列套函数语义偏差结果和预期对不上核对时间口径把口径显式写进提示词行数翻倍输出明显偏多检查 JOIN 条件拿小样本手动验证写操作风险DDL/DML 混入检查语句类型数据库账号只读 提示词禁止5. 接入团队工作流时的安全设计5.1 给查询账号做只读权限AI 生成 SQL 这件事要真正在团队里推广第一道防线不是提示词而是数据库权限。我给数据分析师开的账号默认只给SELECT权限DDL 和 DML 一率不开。这样哪怕 AI 真的生成了一条DELETE或者同事手滑复制错了数据库层面也会直接拦截不会酿成事故。如果公司有条件最好让这些 AI 生成的查询跑在只读从库或者数据仓库副本上。一是避免影响线上业务二是从库的数据量大、分表逻辑清晰适合做数据分析三是权限隔离做起来更方便。我见过一些团队把 AI 生成的 SQL 直接往主库上甩出问题时想回滚都没有余地这个风险实在太大了。5.2 审批、日志与结果审计很多人以为 AI 生成 SQL 的最大风险是语句写得不对其实更大的风险是结果用了没人复核。一个好的流程是AI 生成 SQL - 工程师 review - 生成结果写入日志 - 定期抽检输出。我在团队里推行过一个很简单的方式每次跑完 AI 生成的查询顺手把 SQL 和结果行数记录到一个公开的查询日志表里再用定时任务抽查几个指标跟业务报表对比。连续抽查两周你就能发现哪些 SQL 的口径是稳定的哪些被 AI 悄悄改过口径。审计的意义不在于追责而在于建立“结果可信度”的基准。没有这个基准AI 生成的 SQL 偶尔跑错一次下次就没人敢用了。有了这个基准团队成员会逐渐形成一个共识AI 生成的结果要经过同样严格的校验流程才能算数。这比一边用 AI 一边不信任 AI 要健康得多。5.3 把提示词模板当成代码来管理我最后想强调的一点是提示词模板要像代码一样纳入版本管理。我自己会把常用的表结构说明、业务口径、输出要求全部放在一个 Markdown 文件里提交到内部 Git 仓库。谁要新增一个查询口径先改这个文件再提交 PRreview 通过之后才能作为团队的标准模板使用。这样做的好处很多。第一口径变更可追溯。以前业务方说“客户数口径变了”你可能要翻聊天记录才能想起来上个月怎么写的现在直接看模板的 git 历史就行。第二模板维护成本低。AI 模型在迭代你的模板也要跟着迭代比如某个模型对某类写法总出错你就可以在模板里加一句针对性的要求。第三新同事上手快。新来的数据分析师不用从零开始摸索提示词照着模板改需求描述就行至少不会出现“有人把时间写错、有人输出格式错误”这类基础问题。我在实际操作中的体会是AI 转换 SQL 这个能力并不神秘它就是一个特别熟悉语法但完全不了解你业务的助手。你给它越清楚的表结构、越明确的业务口径、越严格的输出约束它就越接近一个靠谱的组员。反过来如果你把它当成万能工具一句话丢过去就想拿到能直接跑的 SQL那大概率会在字段编造、时间口径、性能陷阱上面反复踩坑。别指望它能替代你的判断力能用好它的人都是用判断力去喂它的。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进