
1. 全站关键字搜索到底难在哪全站关键字搜索说白了就是给一个词让它在整个数据库里所有表、所有文本字段里翻一遍把命中的记录捞出来。听起来简单真做起来坑不少。最直接的问题是表结构不统一。用户表有username、email订单表有order_no、remark商品表有title、description字段名、类型、数量全不一样。你没法写一条固定的 SQL 把全站都覆盖了。第二个问题是字段类型混杂。同样是文本有的是varchar有的是nvarchar还有char、nchar甚至text。如果拼接 SQL 时不加判断遇到非字符型字段直接报类型转换错误。第三个问题是表数量多。一个中等规模的业务库几十上百张表很正常手工写 union 不现实维护成本也高。所以需要一个能自动遍历所有表、自动识别文本字段、动态拼 SQL 的机制。存储过程 游标 动态 SQL 就是干这个的。存储过程负责封装逻辑游标负责逐表逐字段遍历动态 SQL 负责在运行时拼出针对每张表每个字段的查询语句。三者配合才能做到“给一个词全库搜”。这套方案适合谁适合数据库侧做轻量级搜索、不想引入 Elasticsearch 这类外部组件的团队。数据量在百万级以内、对搜索实时性要求不极端的场景用存储过程完全够用。下面我把可复制的骨架、游标遍历、动态 SQL 拼接、以及接入 TaoToken 统一 Key 通道的配置片段都写出来你可以直接拿去改。2. TaoToken 前置统一 Key 与 API 通道准备在讲存储过程之前先把 TaoToken 的接入准备好。为什么要在数据库搜索方案里提 TaoToken因为很多团队搜完数据后需要把结果喂给模型做摘要、分类、或者生成自然语言描述。TaoToken 提供统一的 Key 和 API 通道省得你在多个模型供应商之间来回切换配置。TaoToken 官网是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 入口是 https://taotoken.net/api 。你需要先去控制台创建一个 API Key然后就可以用同一个 Key 调用不同模型。具体操作路径进入控制台 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 在 API Keys 页面 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 生成一个新的 Key。生成后复制保存后面配置里要用。如果你只是想在浏览器里先验证模型能不能通可以直接用模型对话页面 https://taotoken.net/model-chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 输入一句话看返回。确认通道没问题后再回到代码里配置。对于长期做编码、Agent 类任务的场景可以了解 Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 它更适合持续性的开发调用。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面有各语言的调用示例。ClaudeCodeAnthropic 相关配置参考 https://taotoken.net/claudecode-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaudecode-anthropicutm_campaignrewrite 。注意TaoToken 是统一的 API 通道不是数据库组件。它的作用是在你搜索出结果之后把结果送去模型处理。数据库侧的搜索逻辑仍然在存储过程里完成。3. 可复制配置存储过程骨架与游标遍历先建存储过程。核心思路是两层游标外层游标遍历所有用户表内层游标遍历当前表的所有字符型字段。每进入一个字段就拼一条like查询用sp_executesql执行统计命中数。命中数大于 0 就输出该字段的匹配记录。SET ANSI_NULLS ON SET QUOTED_IDENTIFIER ON GO ALTER PROC [dbo].[Full_Search] string VARCHAR(50) AS BEGIN SET NOCOUNT ON; DECLARE tbname VARCHAR(128); DECLARE colname VARCHAR(128); DECLARE sql NVARCHAR(2000); DECLARE hitCount INT; DECLARE resultTable TABLE ( TableName VARCHAR(128), ColumnName VARCHAR(128), MatchValue NVARCHAR(500) ); -- 外层游标遍历所有用户表 DECLARE tbroy CURSOR FOR SELECT name FROM sysobjects WHERE xtype U ORDER BY name; OPEN tbroy; FETCH NEXT FROM tbroy INTO tbname; WHILE FETCH_STATUS 0 BEGIN -- 内层游标遍历当前表的字符型字段 DECLARE colroy CURSOR FOR SELECT c.name FROM syscolumns c INNER JOIN systypes t ON c.xtype t.xtype WHERE c.id OBJECT_ID(tbname) AND t.name IN (varchar, nvarchar, char, nchar); OPEN colroy; FETCH NEXT FROM colroy INTO colname; WHILE FETCH_STATUS 0 BEGIN SET sql NSELECT cnt COUNT(1) FROM [ tbname N] WHERE [ colname N] LIKE kw; EXEC sp_executesql sql, Nkw NVARCHAR(100), cnt INT OUTPUT, kw N% string N%, cnt hitCount OUTPUT; IF hitCount 0 BEGIN INSERT INTO resultTable (TableName, ColumnName, MatchValue) EXEC( NSELECT tbname N, colname N, CAST([ colname N] AS NVARCHAR(500)) FROM [ tbname N] WHERE [ colname N] LIKE % string N% ); END FETCH NEXT FROM colroy INTO colname; END CLOSE colroy; DEALLOCATE colroy; FETCH NEXT FROM tbroy INTO tbname; END CLOSE tbroy; DEALLOCATE tbroy; -- 输出汇总结果 SELECT TableName, ColumnName, MatchValue FROM resultTable ORDER BY TableName, ColumnName; END GO这段代码有几个关键点。第一用syscolumns和systypes联查只取字符型字段避免对int、datetime做like报错。第二sp_executesql用参数化传kw比直接拼字符串安全能防注入。第三结果先插入表变量再统一输出方便你后续加工。如果你用的是 SQL Server 2005 及以上sysobjects和syscolumns仍然可用但更推荐用sys.tables和sys.columns。下面是兼容新版的字段遍历写法DECLARE colroy CURSOR FOR SELECT c.name FROM sys.columns c INNER JOIN sys.types t ON c.user_type_id t.user_type_id WHERE c.object_id OBJECT_ID(tbname) AND t.name IN (varchar, nvarchar, char, nchar);把这段替换掉原来的内层游标定义即可。实测下来新版系统视图在字段类型判断上更准确尤其是自定义类型的情况。4. 验证请求与成功结果存储过程建好后直接调用测试。假设你要搜“订单”这个词EXEC dbo.Full_Search string 订单;执行后会返回一个结果集三列TableName、ColumnName、MatchValue。比如TableNameColumnNameMatchValueOrdersRemark客户催订单发货ProductsTitle订单专用包装盒LogsContent订单创建成功这说明搜索通道跑通了。如果结果为空先确认数据库里确实有包含该关键字的记录再检查字段类型是否在varchar/nvarchar/char/nchar范围内。text和ntext类型不在当前游标范围内需要单独处理。接下来把搜索结果接入 TaoToken。假设你用 Python 做后端搜完数据后调用模型做摘要import requests TAOTOKEN_API https://taotoken.net/api API_KEY 你的_TaoToken_Key def summarize_search_results(keyword, results): prompt f以下是全站搜索关键字「{keyword}」的结果\n for r in results: prompt f- 表{r[TableName]} 字段{r[ColumnName]}{r[MatchValue]}\n prompt \n请用一段话总结这些结果的核心信息。 resp requests.post( f{TAOTOKEN_API}/v1/chat/completions, headers{ Authorization: fBearer {API_KEY}, Content-Type: application/json }, json{ model: gpt-4o-mini, messages: [{role: user, content: prompt}] } ) return resp.json()[choices][0][message][content]调用后你会拿到一段自然语言摘要比如“搜索结果显示订单相关记录集中在 Orders 表的 Remark 字段和 Products 表的 Title 字段主要涉及发货催单和包装物料”。这样数据库搜索 模型摘要的链路就完整了。提示TaoToken 的 API 地址是 https://taotoken.net/api 不要漏掉/api路径。Key 放在Authorization头里格式是Bearer 你的Key。5. 本篇常见错排查第一个常见错误拒绝访问或对象名无效。这通常是存储过程创建时没有加dbo.前缀或者当前登录用户没有执行权限。解决方法是创建时写CREATE PROC dbo.Full_Search执行时写EXEC dbo.Full_Search。权限问题让 DBA 给EXECUTE权限即可。第二个错误将 varchar 值转换成 int 列时失败。这说明游标把非字符型字段也遍历进来了。检查systypes的过滤条件确保只保留varchar、nvarchar、char、nchar。如果你用的是sys.types注意用user_type_id关联不要用xtype。第三个错误搜索结果重复。因为同一个值可能在多个字段命中或者LIKE匹配到了多条记录。可以在最终输出时加DISTINCT或者在插入表变量时去重。如果业务允许重复保留原样也行。第四个错误执行超时。表多、数据量大时逐字段COUNT会很慢。优化方向是限制遍历的表范围比如只搜业务表排除日志表、临时表。可以在外层游标加AND name NOT LIKE %Log%之类的过滤。另一个方向是给常用搜索字段建索引但LIKE %kw%前置通配符用不上索引这是LIKE的固有限制。第五个错误TaoToken 调用返回 401。检查 Key 是否复制完整有没有多余空格。如果用的是环境变量确认变量名和读取方式一致。401 一般是认证失败403 可能是 Key 权限不足或额度用完。去控制台 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 看一下 Key 状态和用量。第六个错误模型返回内容为空。检查请求体里model字段是否拼写正确messages是否至少有一条user消息。如果用的是流式接口解析方式不同非流式接口直接取choices[0].message.content。6. 接入文档与后续调用建议存储过程跑通、TaoToken 调通之后日常使用就是两步先EXEC dbo.Full_Search string 关键词拿结果再把结果拼成 prompt 发给 TaoToken。如果你要做成 Web 服务把这两步包在一个接口里前端传关键词后端返回摘要。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面有 Python、Node.js、Go 等语言的完整示例。API Keys 管理在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 建议给不同环境建不同的 Key方便排查和限额。长期做编码类任务的话Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 比按次调用更划算。ClaudeCodeAnthropic 的配置参考 https://taotoken.net/claudecode-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaudecode-anthropicutm_campaignrewrite 适合在 IDE 里直接调用。最后说一个实用技巧存储过程里的string参数长度设成VARCHAR(50)可能不够如果你的关键词可能更长改成NVARCHAR(200)。另外LIKE %kw%对大小写敏感取决于数据库排序规则如果要不区分大小写用COLLATE Chinese_PRC_CI_AS或者在拼接时统一转小写。这些细节调一次就记住了。