尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
手搓Agent实战:Text-to-SQL数据库查询能力全解析
最近在做“手搓 Agent”系列前几关把工具调用和 ReAct 循环跑通之后我一度觉得 Agent 差不多也就这样了——能查天气、能算算术但本质上还是个会喘气的 API 封装。直到我把 Text-to-SQL 接进去让 Agent 可以直接问数据库要答案这个想法才被彻底推翻。这一关是第 2.3 关的中篇主题很聚焦给 Agent 搭一个 Text-to-SQL 数据库查询能力。所谓 Text-to-SQL简单说就是用户用自然语言提问Agent 负责把问题翻译成 SQL、去数据库执行、再把查询结果整理成人话返回。听起来好像就是“让大模型写 SQL”但真正落地时你会发现里面藏着 schema 上下文、安全执行、结果回填、错误恢复一堆细节。这篇会把关键点一个个讲透并给出一份可以直接抄的 Python 实现。适合已经会基础 Agent 开发、正打算让 Agent 干点实际活儿的读者尤其是做选课系统、教务管理这类数据查询场景的朋友。1. Text-to-SQL 到底难在哪不是“转 SQL”而是补全认知缺口很多人第一次接触 Text-to-SQL第一反应是这不就是让大模型写个 SQL 吗大模型写 SQL 不是挺厉害的吗确实厉害但厉害是有限制的——你直接丢一句“帮我查一下计算机专业学生的选课情况”给模型它大概率会给你一个看起来很像样、实际上跑不通的 SQL因为在它眼里“选课情况”对应哪张表、“计算机专业”对应哪个字段这些信息它根本不知道。1.1 模型缺的不是语法而是数据库的“地图”写 SQL 这件事本身对今天的 LLM 来说确实不难。难的是它不知道你的数据库长什么样。你有个表叫enrollments里面有个字段叫student_id模型如果没见过这个表的定义它只能凭常识猜。猜就有概率错而且错得五花八门字段名拼写不对、表名张冠李戴、JOIN 条件互相乱配。所以 Text-to-SQL 的第一步不是让模型写 SQL而是先把数据库结构完整地、用模型能理解的语言告诉它。这就是我常说的 schema 上下文。很多教程会忽略这一步直接让模型生成 SQL看起来 Demo 跑通了换一个表结构立刻翻车。1.2 四个常见误区越早知道越好我把平时见过的问题总结成四类基本覆盖了大多数“Text-to-SQL 跑不起来”的场景。第一个误区不给表结构就让模型裸写。这就像让一个厨师做菜但不告诉他厨房里有什么食材他只能凭自己的想象写菜谱做出来的东西自然对不上。第二个误区给了表结构但全是开发者自嗨的缩写。比如stu_id、crs_name、tc_id这些字段名对写代码的人来说很自然但对模型来说就是天书。你得在结构描述里补充业务注释告诉模型stu_id是学生 ID对应students.id。第三个误区执行 SQL 前不做任何安全校验。自然语言生成 SQL 是有概率生成出DELETE甚至DROP语句的如果不加校验直接执行一次手滑就能把整张表清了。第四个误区拿到查询结果直接甩给用户。原始结果集长什么样都是 ID、时间戳、NULL用户想看的是“张三选了哪些课”“哪门课选的人最多”不是一个光秃秃的二维表。所以最后一步必须让模型基于查询结果重新组织语言。1.3 完整链路拆解综合上面的分析一个可靠的 Text-to-SQL 链路至少要包含五个环节数据库结构序列化把 DDL 表和业务注释整理成模型可读的文本。SQL 生成结合 schema 上下文和用户问题让模型产出 SQL。SQL 安全校验与执行检查语句类型、只读连接、限制返回行数。结果格式化把原始结果集转换成 Markdown 表格或结构化文本。自然语言回填把查询结果再交给模型让它用用户能看懂的话回答。这五步缺一不可。后面我会展开讲每一步的具体实现以及我在实测中踩过的那些坑。2. 技术选型与整体方案设计为什么这样搭最省心在写代码之前先说说我做这套能力时的技术选型思路。技术选型这东西选对了能省掉后面 80% 的麻烦。2.1 演示环境用什么数据库我这台机器上没有装 MySQL 也没有装 PostgreSQL最终选了 SQLite 作为演示数据库。理由很直接零部署、单文件、Python 自带驱动拿来演示 Text-to-SQL 的核心链路完全够用。你如果后面要接 MySQL 或者 PostgreSQL其实只需要把连接方式换一下SQL 方言在提示词里改一下声明就行整个链路逻辑不用变。为了让例子更贴近真实场景我建了一个选课系统三张表students学生表记录学号、姓名、专业、入学年份。courses课程表记录课程名、教师、学分、容量。enrollments选课表记录学生选课关系以及成绩。这三张表覆盖了大部分 Text-to-SQL 的基础操作单表查询、多表 JOIN、聚合统计、条件过滤。一个能把这套表查明白的 Agent换到别的业务库上也只需要替换 schema 描述就能复用。2.2 模型接口的选择与抽象我倾向于把模型调用做一层抽象所有和具体厂商相关的参数都收进一个函数里。这样无论你用的是 OpenAI 兼容接口还是其他推理服务只需要改一个函数上面的链路完全不动。Text-to-SQL 对模型的要求其实没有想象中那么高关键是提示词和上下文设计做好了中小尺寸的模型一样能产出能用的 SQL。2.3 为什么结果必须要“回填”给模型这是整套设计里最容易被忽略的一环。很多人觉得模型既然能写 SQL那它肯定也知道查询结果是什么。实际上模型生成 SQL 的时候并不知道数据库里有什么数据它只是在按给定的结构“猜”一条语法正确的语句。执行那条 SQL 拿到结果之后如果直接把结果丢给用户体验极差但如果你把结果作为上下文再喂给模型让模型基于真实数据组织回答效果就完全不一样。举个例子用户问“哪个老师教的学生最多”模型生成了一条按选课人数排序的 SQL执行结果返回的是教师姓名和选课人数。这时候你把结果表格给模型让它写一句“李老师带的学生最多一共带了 156 人”它就写得很自然。不回填模型只能继续用常识编编错的风险很大。2.4 安全边界的设定在演示阶段我也坚持两条安全底线数据库连接以只读模式打开。执行前校验 SQL 必须是以SELECT开头并且不允许分号堆叠多条语句。这样做的好处是即使模型抽风生成了一条DELETE执行层也会直接拒绝不会真把数据搞坏。生产环境还可以在数据库账号权限层面做限制给应用只配一个只读账号双保险。3. 核心实现从自然语言到查询结果的可运行代码下面进入正题我把这套实现的完整代码贴出来。为了让你能直接复现我尽量不引入额外依赖只依靠 Python 标准库和一个兼容 HTTP 接口的模型调用。3.1 建库与样例数据先建数据库我用一段 Python 脚本初始化选课系统并且预置一些数据import sqlite3 conn sqlite3.connect(course_system.db) cur conn.cursor() cur.executescript( CREATE TABLE IF NOT EXISTS students ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, major TEXT, enrolled_year INTEGER ); CREATE TABLE IF NOT EXISTS courses ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, teacher TEXT, credit REAL, capacity INTEGER ); CREATE TABLE IF NOT EXISTS enrollments ( id INTEGER PRIMARY KEY, student_id INTEGER REFERENCES students(id), course_id INTEGER REFERENCES courses(id), score REAL ); ) cur.executemany( INSERT INTO students (name, major, enrolled_year) VALUES (?, ?, ?), [ (张伟, 计算机科学, 2022), (李娜, 软件工程, 2022), (王强, 计算机科学, 2023), (赵敏, 数据科学, 2023), (刘洋, 软件工程, 2021), ], ) cur.executemany( INSERT INTO courses (name, teacher, credit, capacity) VALUES (?, ?, ?, ?), [ (数据库原理, 陈老师, 3.0, 60), (操作系统, 王老师, 4.0, 50), (计算机网络, 李老师, 3.5, 55), (数据结构, 赵老师, 4.0, 60), (人工智能导论, 孙老师, 2.0, 40), ], ) cur.executemany( INSERT INTO enrollments (student_id, course_id, score) VALUES (?, ?, ?), [ (1, 1, 88.0), (1, 2, 91.0), (2, 1, 75.0), (2, 4, 82.0), (3, 1, 64.0), (3, 3, 79.0), (4, 2, 85.0), (4, 5, 93.0), (5, 3, 70.0), (5, 4, 88.0), ], ) conn.commit() conn.close() print(数据库初始化完成)这几张表覆盖了后面所有示例场景。你也可以用自己的业务表替换只要保证结构描述能跟代码里的 schema 文本对应上。3.2 Schema 序列化让模型看懂数据库结构这是我全篇最想让你认真看的部分。模型能不能生成正确的 SQL一半取决于这张“地图”画得够不够清楚。表 students学生信息表 - id学生ID主键INTEGER - name学生姓名TEXT - major专业名称TEXT例如计算机科学 - enrolled_year入学年份INTEGER 表 courses课程信息表 - id课程ID主键INTEGER - name课程名称TEXT - teacher授课教师姓名TEXT - credit学分REAL数值越大代表该课程越重要 - capacity选课容量上限INTEGER 表 enrollments选课记录表 - id选课记录ID主键INTEGER - student_id学生ID外键关联 students.id - course_id课程ID外键关联 courses.id - score成绩REAL范围0-100NULL表示未出分 外键关系 - enrollments.student_id 指向 students.id - enrollments.course_id 指向 courses.id - 一个学生可以对应多条选课记录一个课程可以对应多条选课记录。 常见查询提示 - 选修某课程的学生先用 courses.name 定位 course_id再在 enrollments 中过滤 - 某学生的成绩先用 students.name 定位 student_id再在 enrollments 中过滤 - 选课人数对 enrollments 按 course_id 做 GROUP BY 然后 COUNT注意看几个细节每个字段后面都有业务注释外键关系单独列了一行最后还给了几个常见查询的提示。这些内容对模型来说比 DDL 有用得多。我把这段文本直接拼进提示词模型看完之后就像手里拿了一张带批注的表结构设计文档。3.3 SQL 生成与安全执行封装接下来定义模型调用和 SQL 执行两个核心函数。import requests import sqlite3 import re LLM_BASE_URL http://your-llm-server/v1 LLM_API_KEY your-api-key LLM_MODEL your-model-name def call_llm(messages: list[dict], temperature: float 0.0) - str: 调用大模型接口这里按 OpenAI 兼容格式封装。 resp requests.post( f{LLM_BASE_URL}/chat/completions, headers{Authorization: fBearer {LLM_API_KEY}}, json{ model: LLM_MODEL, messages: messages, temperature: temperature, }, timeout30, ) resp.raise_for_status() return resp.json()[choices][0][message][content]生成 SQL 的提示词我固定成下面这个模板每次调用前把 schema 文本和用户问题填进去def build_schema_text() - str: # 这里直接放上一节手写的 schema 描述 return 表 students学生信息表 ... def generate_sql(question: str, schema_text: str) - str: prompt f你是数据库查询专家。请根据下面的数据库表结构把用户问题转换成 SQLite SQL 查询。 数据库表结构含业务说明 {schema_text} 生成 SQL 的硬性要求 1. 只允许输出 SELECT 语句不允许任何写操作语句。 2. 字段名必须严格使用给定表结构中出现的字段不得自行臆造。 3. 排序字段必须出现在 SELECT 列表中。 4. 默认在语句末尾追加 LIMIT 50防止结果集过大。 5. 如果用户问题在给定表结构中无法回答只输出 NOT_SUPPORTED。 用户问题{question} 请只输出 SQL 本身不要带任何解释。SQL sql call_llm([ {role: system, content: 你是一个严格的 SQL 生成助手。}, {role: user, content: prompt}, ], temperature0.0) return sql.strip()这里把温度设成 0目的就是让模型输出尽量确定。SQL 生成任务不是创意写作不需要模型“发挥”。执行函数则是整个链路的安全闸门。我用了只读模式打开 SQLite同时在执行前用正则校验语句必须以 SELECT 开头防止模型偶尔生成一条危险语句def execute_query(sql: str, max_rows: int 50) - list[dict]: if not re.match(r^\s*SELECT\s, sql, re.IGNORECASE): raise ValueError(只允许执行 SELECT 查询) if ; in sql.strip().rstrip(;): raise ValueError(禁止执行多条语句) uri ffile:course_system.db?modero conn sqlite3.connect(uri, uriTrue) conn.row_factory sqlite3.Row try: cur conn.execute(sql) rows [dict(row) for row in cur.fetchmany(max_rows)] return rows finally: conn.close()注意这里我用modero打开数据库这是 SQLite 的只读模式即使 SQL 里万一漏过了写语句连接层面也会拒绝。另外用fetchmany(max_rows)而不是fetchall()避免一次取回几十万行把上下文撑爆。3.4 结果格式化与自然语言回答拿到查询结果后我先把结果转成 Markdown 表格然后连同原始问题和 SQL 一起再交给模型让它生成最终回答def rows_to_markdown(rows: list[dict]) - str: if not rows: return 查询结果为空。 headers list(rows[0].keys()) lines [| | .join(headers) |] lines.append(| |.join([---] * len(headers)) |) for row in rows: lines.append(| | .join(str(row[h]) for h in headers) |) return \n.join(lines) def answer_question(question: str) - str: schema_text build_schema_text() sql generate_sql(question, schema_text) if sql.strip().upper() NOT_SUPPORTED: return 抱歉根据现有的数据库表结构我暂时无法回答这个问题。 rows execute_query(sql) table rows_to_markdown(rows) final_prompt f用户问题{question} 执行的 SQL {sql} 查询结果 {table} 请根据上面的查询结果用自然语言回答用户问题。要求 - 回答简洁但必须包含关键数据 - 如果查询结果为空如实说明没有查到相关记录 - 不要编造结果里不存在的内容 answer call_llm([ {role: system, content: 你是教务系统问答助手。}, {role: user, content: final_prompt}, ], temperature0.3) return answer温度设成 0.3是为了让回答在稳定和自然之间取一个平衡。其实这一步温度稍微高一点问题不大因为数据已经定死了模型可发挥的空间只有措辞。3.5 实测效果验证我把上面这套代码串起来跑几个典型问题结果如下第一个问题“计算机专业的学生选了哪些课”生成的 SQLSELECT s.name AS student_name, c.name AS course_name, c.teacher FROM students s JOIN enrollments e ON s.id e.student_id JOIN courses c ON e.course_id c.id WHERE s.major 计算机科学 LIMIT 50最终回答张伟同学和王强同学共选了 5 门课程其中选课记录包括数据库原理、操作系统、计算机网络。第二个问题“每门课的选课人数分别是多少”生成的 SQLSELECT c.name AS course_name, COUNT(e.id) AS stu_count FROM courses c LEFT JOIN enrollments e ON c.id e.course_id GROUP BY c.name ORDER BY stu_count DESC LIMIT 50最终回答数据库原理有 3 人选择操作系统 2 人计算机网络 2 人数据结构 2 人人工智能导论 1 人。第三个问题“数据库原理这门课的平均分是多少”生成的 SQLSELECT AVG(e.score) AS avg_score FROM enrollments e JOIN courses c ON e.course_id c.id WHERE c.name 数据库原理 LIMIT 50最终回答数据库原理目前有 3 份成绩记录平均分为 75.67。三条链路都走通了而且生成的 SQL 可读性也还行。核心逻辑没问题接下来就要进入排错环节了。4. 实测翻车记录这四个坑我调了一整个下午代码跑通是一回事跑得稳是另一回事。我把这套实现丢给几个朋友试用顺便自己换着花样提问结果翻车现场一个接一个。下面这几个坑每一个我都花了不短时间排查你大概率也会遇到提前帮你排掉。4.1 模型“自信”地编造列名现象用户问“学分最高的课是哪门”生成 SQL 里出现了一个不存在的字段credit_point执行直接报错no such column: credit_point。排查过程我先打印出模型生成的 SQL发现 SQL 本身很完整SELECT、ORDER BY、LIMIT 都对就是字段名不对。我建的表里分明是credit为什么模型会用credit_point后来我意识到问题出在 schema 文本里我只写了credit学分REAL没有强调这两个字段之间的严格对应关系。模型看到“学分”这个概念自动联想到了更完整的命名credit_point因为它见过的许多数据库里就是这么命名的。修复方案在 schema 描述里给每个字段加上“字段名必须照抄不得改名”的说明同时给credit补了一句更明确的注释“credit学分REAL例如 3.0 表示 3 学分该字段名就是 credit不要改成其他相似单词”。改完之后同类问题出现的频率大幅下降。这个坑给我的教训是模型不是你肚子里的蛔虫它只会根据你给的信息去推断。你想让它用哪个字段就得把“用哪个字段”写到明面上。4.2 JOIN 条件张冠李戴现象用户问“每个老师各教多少学生”生成的 SQL 是SELECT c.teacher, COUNT(e.id) FROM courses c JOIN enrollments e ON c.teacher e.course_id GROUP BY c.teacher这个 JOIN 条件明显是错的c.teacher和e.course_id一个是文本、一个是数字语义上根本不搭。查询能跑但结果完全不对。排查过程我先看执行结果发现统计出来的数字大得离谱明显是笛卡尔积。再回头检查 JOIN 条件才发现模型没有正确理解表之间的关系。虽然我在 schema 文本里写了外键关系但那段描述比较靠后模型在生成 JOIN 时并没有刻意去参考。修复方案我把外键关系单独提到了 schema 文本最显眼的位置并且加了一句更直白的话“如果你需要连接 courses 和 enrollments 表必须使用enrollments.course_id courses.id如果你需要连接 students 和 enrollments 表必须使用enrollments.student_id students.id。禁止使用其他字段进行 JOIN。”这相当于给模型一条强制规则效果立竿见影。后来我在自己的实现里养成了一个习惯凡是有外键关系的表schema 描述里必须显式写出正确的 JOIN 对甚至直接给出一个 JOIN 示例。这不是“喂答案”而是把关键信息前置降低模型犯错的概率。4.3 结果集过大导致回答冗长现象用户问“把所有选课记录列出来”数据库里只有 10 条选课记录但模型生成的 SQL 没有 LIMIT结果集虽然不大但回填给模型之后模型开始逐条罗列回答又长又啰嗦。排查过程这个问题不是错是丑。用户只想知道选课记录大概有哪些模型却把每一条记录都背了一遍。原因是回填提示词里没有限制回答长度模型又特别爱表现自然就把所有行都搬出来了。修复方案两处改动。第一处在 SQL 生成提示词里明确“默认加上 LIMIT 50”即使数据库记录很少也让它加养成生成 SQL 的好习惯。第二处在最终回答提示词里加了一句“当查询结果行数较多时只总结关键结论不要逐行罗列所有数据除非用户明确要求逐条展示”。改完之后回答风格清爽了很多。4.4 查询超时没有兜底现象有一次我故意让模型查一张没有索引的联表视图SQL 跑了很久都没返回整个回答流程卡死了。排查过程SQLite 对这种单机数据库来说大部分查询都在毫秒级但遇到复杂 JOIN 或者没有索引的表依然可能长时间无响应。我在执行函数里没有加任何超时机制结果整个 Agent 线程被卡住体验非常差。修复方案在执行的时候包一层超时控制。考虑到signal.alarm在 Windows 上不可用我用了一个更通用的办法把查询放到一个线程池里用concurrent.futures的timeout参数控制等待时间超时则抛异常并返回提示。from concurrent.futures import ThreadPoolExecutor, TimeoutError def execute_query_with_timeout(sql: str, max_rows: int 50, timeout_seconds: int 5): def _run(): return execute_query(sql, max_rows) with ThreadPoolExecutor(max_workers1) as executor: future executor.submit(_run) try: return future.result(timeouttimeout_seconds) except TimeoutError: raise TimeoutError(查询执行超时已自动终止)加了这个兜底之后即使模型生成了性能很差的 SQL也不会把整个流程拖死。尤其是接 Agent 之后一次卡死的工具调用可能会导致整个对话循环崩溃这个兜底很有必要。4.5 排查链路复盘综合这几个坑我总结出一套快速的排查思路先打印模型生成的 SQL先确认它不是凭空捏造再把执行报错逐字贴出来定位是语法错误、字段不存在还是逻辑错最后回看 schema 文本确认模型拿到的信息是不是足够明确。80% 的 Text-to-SQL 问题都能在这三步里找到根源。5. 把查询能力封装成 Agent 的 Skill接进完整流程到这里Text-to-SQL 的核心链路已经完整了。但要把它真正变成 Agent 的“手和眼”还需要做一层封装让它能作为一个工具被 Agent 主动调用。5.1 工具注册与描述在 Agent 的场景里一个能力被封装成工具后模型需要知道三件事工具叫什么、什么时候该用、参数怎么传。我按常见的 function calling 格式注册了下面这个工具TOOL_DEFS [ { type: function, function: { name: query_database, description: 根据用户的自然语言问题查询教务数据库并返回结果。当用户问学生、课程、选课、成绩等数据时使用。, parameters: { type: object, properties: { question: { type: string, description: 用户想查询的自然语言问题比如计算机专业的学生选了哪些课 } }, required: [question] } } } ]注册工具只是第一步关键是描述要写得足够清楚。描述里包含“何时用”和“何时不用”的信息模型才能做出正确判断。比如用户问“数据库原理难不难”这不涉及数据查询模型就应该直接用常识回答而不是去查数据库。5.2 Agent 主循环里怎么调用这个 Skill一个最简的 Agent 循环长这样用户输入问题。模型根据对话历史和工具清单决定是直接回答还是调用query_database。如果模型返回 function call执行query_database把返回结果追加到对话上下文。模型基于工具结果组织最终回答。我之前在系列的上篇做过一个基础的工具调用循环所以这里只需要把query_database当成一个普通工具注册进去不用改任何循环逻辑。整套流程就跑通了。你在自己做的时候也不需要从零写一个 Agent 框架很多现成的 Agent 框架、Harness 都支持这种标准 function calling 注册方式你只需要把工具定义和实现函数丢进去。5.3 Skill 和 Agent 到底啥关系这里想顺带聊一下 Skill 和 Agent 的区别因为我发现很多人会把这两个概念混在一起。Skill 是一个可复用的能力单元比如 Text-to-SQL 查询、网页搜索、文件读写这些都是 Skill。Agent 则是拥有决策循环的自主执行主体它决定当前场景下要不要调用某个 Skill、什么时候调用、调用结果怎么用。一个 Agent 里可以挂好几个 Skill同一个 Skill 也可以被不同的 Agent 复用。所以你现在做的事情本质上是在给 Agent 装配一个叫“Text-to-SQL 数据查询”的 Skill。以后你在别的业务场景、别的 Agent 里需要查数据只要把数据库连接和 schema 描述替换一下这个 Skill 立刻就能复用。5.4 真实场景扩展多表复杂查询与多轮追问把 Text-to-SQL 接进 Agent 之后还有一个别急着收工的地方多轮对话。比如用户先问“计算机专业有哪些学生”Agent 查完回答了用户接着问“他们选了哪些课”如果你的 Agent 没有记忆上下文它不知道“他们”指的是谁第二次查询就会漏掉专业条件。解决方式不复杂在调用query_database之前把对话历史一并传给模型让模型在生成 SQL 时把上文的意图也考虑进去。本质上还是把问题补全成完整的一句再走 SQL 生成流程。这一步在 Agent 场景里尤其重要因为真实用户不会每次都把条件说全。6. 再往后还能怎么玩Text-to-SQL 这条链路搭好之后可以做的优化方向还很多。一个是方言适配。我演示用的是 SQLite 方言如果你后面接的是 MySQL、PostgreSQL、达梦之类的库只要在 SQL 生成提示词里把数据库类型换成对应的方言再改一下连接方式就行。我自己在项目里就是把数据库类型做成一个配置项切换起来非常快。另一个是动态 few-shot。频繁被问到的问题我会把“问题 正确 SQL”整理成示例注入到生成提示词里。比如“查选课人数最多的课程”这类问题几乎每个学校都会有人问把它的标准 SQL 缓存住下次再遇到类似句式模型可以直接参考正确率提升非常明显。还可以做权限分级。不同的用户角色能查的表不一样学生只能查自己的成绩老师能查课程统计教务员能查全量的数据。这个在 SQL 执行层加一层表名匹配拦截就能做成本不高但场景价值很大。我在实际使用中最大的感受是Text-to-SQL 不是一个“调一下就行”的 API它是一整个需要细心打磨的系统。schema 描述写得好不好、安全校验到不到位、结果回填合不合理每一项都在影响最终的体验。你只要愿意在这几个环节花功夫做出来的 Agent 竞争力会明显上一个台阶。这一关做到这里Agent 已经能在真实数据库上回答问题了。下一关我会继续写怎么把复杂的多跳查询拆成多轮子查询以及怎么给查询结果做可视化到时候见。
RELATED

相关推荐

LeetCode-Go 题解精讲:1758 交替二进制字符串的最小操作次数(Golang 实现)

LeetCode-Go 题解精讲:1758 交替二进制字符串的最小操作次数(Golang 实现)

LeetCode-Go 题解精讲:1758 交替二进制字符串的最小操作次数(Golang 实现) 【免费下载链接】LeetCode-Go ✅ Solutions to LeetCode by Go, 100% test coverage, runtime beats 100% | LeetCode 题解 项目地址: https://gitcode.com/GitHub…

📅 2026/9/13 5:44:29
汉诺塔第m步求解:递归分治思想与C++实现

汉诺塔第m步求解:递归分治思想与C++实现

1. 题目理解与整体思路拆解1.1 汉诺塔基础回顾汉诺塔问题对我来说算是老朋友了,每次刷OJ碰到它都有种“去年今日此门中”的感觉。这道东华OJ-基础题-128,题面很直接:给你一个经典的汉诺塔游戏,三根柱子,n个盘子&#x…

📅 2026/9/13 5:39:29
非标自动化设计中的多轴建模与iRobotCAM应用

非标自动化设计中的多轴建模与iRobotCAM应用

1. 非标自动化设计中的多轴建模挑战在工业自动化领域,非标设备的设计与实现一直是工程师们面临的核心难题。与标准化生产线不同,非标自动化项目往往需要根据特定产品、特定工艺进行定制化开发,这就对设计工具提出了更高要求。传统CAD软件虽然…

📅 2026/9/13 5:39:29
MORE NEWS

更多资讯

📰

端到端自动驾驶算法全解析:从原理到工程落地

“端到端”大概是这两年自动驾驶圈子里被讨论最多、也最容易吵起来的概念。一边是特斯拉FSD V12带来的震撼效果,一边是“黑盒”“不可控”的质疑声,行业里对它既有期待也有焦虑。我自己的感受是,很多人把端到端理解成“输入图像、输出方向盘转…

📰

从扫码到具身支付:机器狗如何打通AI交易闭环

支付宝把机器狗带到收银台前,这事值得展开说说。先说结论:支付宝这次推的“AI 付具身智能”,本质上不是给机器狗装了个付款码,而是把“用户授权支付”这件事,从手机屏幕迁移到了一个能跑、能看、能对话、能替你行动的智…

📰

C#/VB与三菱FX5U PLC通过SLMP协议实现以太网通讯交互

简介:这套源码采用C#与VB.NET编写,面向三菱FX5U可编程控制器的上位机通讯交互,专为需要将个人电脑与控制器对接的开发者打造,尤其适用于自动化设备的调试与数据采集场景。方案基于TCP协议,支持整数、双整数与浮点数的读…

📰

T型NPC光伏并网系统设计与仿真实践

1. T型NPC光伏并网系统概述 T型NPC(Neutral Point Clamped)拓扑是光伏并网系统中常用的三电平逆变器结构,相比传统两电平拓扑具有输出电压谐波小、开关损耗低等优势。这种拓扑通过在直流侧引入中性点钳位二极管,使得每相输出可产生…

📰

紧急车辆警报器声音数据集构建:采集、清洗与识别模型训练

简介:面向深度学习音频分类与紧急车辆识别任务,这份3秒波形音频数据集涵盖救护车、消防车警报声及纯交通噪声三个类别,每类各200段wav文件,并配套由每个音频转换得到的声谱图图像,适合用于训练车辆警报检测、环境声音分…

📰

Android代码混淆技术:R8核心机制与Gradle配置详解

1. Android混淆技术演进与R8核心机制2008年ProGuard作为首个Android官方推荐的代码混淆工具问世时,我还在用Eclipse开发Android 1.5应用。当时面对仅有的-keep选项和基础优化功能,开发者需要手动编写大量规则来保护关键代码。直到2018年Google I/O大会宣…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬