SQL Server 空表和非空表查出来对不上?把两段 SQL 丢给走 TaoToken 的 Codex 对照

发布时间:2026/9/20 10:33:24
SQL Server 空表和非空表查出来对不上?把两段 SQL 丢给走 TaoToken 的 Codex 对照 在 SQL Server 里做数据库巡检时很多朋友都会遇到一个很别扭的现象明明用游标一段一段count(*)算出来的空表清单和用sys.partitions、sysindexes查出来的非空表清单放在一起对不上。同一张表一边说它是空的另一边却把它算进了非空表。这时候你很难判断到底是脚本写歪了还是统计信息没刷新。本篇就围绕这个对账场景把两段 SQL 一起丢给走 TaoToken 的 Codex 做逐行核对。TaoToken 官网是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 注册后创建 Key把 Codex 的 Base URL 指向 https://taotoken.net/api SQL 仍然在你本地 SQL Server 里执行Codex 只负责帮你把判定口径的差异找出来。一、原问题与场景两套判定口径为什么会打架先把原文里的两套写法摆出来这样后面核对才有依据。第一套是查空表思路是用游标遍历sysobjects里xtypeU的用户表逐表拼sp_executesql做count(*)谁算出来是 0 就打印谁use 库名 go declare tablename nvarchar(100) declare sql nvarchar(2000) declare count int declare a int declare cur_c cursor for select name from sysobjects where xtypeU and status0 open cur_c fetch next from cur_c into tablename while fetch_status 0 begin set sqlselect acount(*) from tablename exec sp_executesql sql,Na int output,count output if count0 print tablename fetch next from cur_c into tablename end close cur_c deallocate cur_c第二套是查非空表原文给了两种一种走存储区一种走索引表-- 根据存储区来判断 select B.name from sys.partitions A inner join sys.objects B on A.object_idB.object_id where B.typeU and A.rows0 -- 根据索引表来判断 select B.name from sysindexes A inner join sys.objects B on A.idB.object_id where B.typeU And A.rows 0问题就出在这两套口径的“数据来源”根本不是一回事。游标那套是实时算行数每张表都真真切切跑了一次count(*)结果反映的是当下这一刻表里到底有没有数据。而sys.partitions和sysindexes里的rows是统计信息它来自存储引擎维护的行数估算可能因为批量导入、删除、分区切换、统计信息未更新等原因和真实行数存在偏差。sysindexes更是老版本兼容视图在较新的 SQL Server 里它的rows字段语义和sys.partitions也不完全一致。于是就会出现一张表刚被DELETE清空count(*)已经是 0但sys.partitions.rows还停留在旧值于是它同时出现在“空表”和“非空表”两个结果里。反过来一张表刚插入数据但统计信息还没跟上count(*)不为 0sys.partitions.rows却还是 0它就会从两边都消失。读者看到这种对不上的结果第一反应往往是“我脚本是不是写错了”其实很多时候是判定口径本身就不统一。这条内容要做的动作很明确把上面这两段 SQL 一起贴给 Codex让它逐行核对where条件里的xtype、status0、A.rows0这些差异指出哪张表的判定会在两种写法下打架最后给出一个统一的空表/非空表查询写法。二、TaoToken 前置给 Codex 配好 Key 和 Base URL在开始对账之前先把 Codex 的接入配好。TaoToken 在这里的角色很单纯就是给 Codex 提供 Key 和 Base URL不参与 SQL 执行也不碰你的数据库。第一步打开 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 注册账号。注册完成后进入控制台在 API Keys 页面创建一个新的 Key。这个 Key 就是后面 Codex 调用时要填的凭证形如YOUR_API_KEY请替换成你自己创建出来的那一串。第二步把 Codex 的 Base URL 填成https://taotoken.net/api。注意这里不要带任何多余路径就是到/api为止。模型 ID 按你在 TaoToken 控制台里选定的模型填写即可。如果你用的是命令行方式接入可以先安装 CLInpm i -g taotoken/taotoken然后用类似下面的方式启动taotoken cc -k YOUR_API_KEY -u https://taotoken.net/api -m MODEL_ID其中-k后面换成你自己的 Key-u固定为https://taotoken.net/api-m换成你要用的模型 ID。这样 Codex 就通过 TaoToken 拿到了对话能力接下来你贴进去的 SQL 它就能逐行分析。需要再强调一次TaoToken 只负责给 Codex 供 Key 和 Base URL真正的 SQL 还是由你在本地 SQL Server 里执行。Codex 给的是判定逻辑上的对照和改法建议最终结果要以你本地跑出来的为准。三、可复制配置把两段 SQL 一起交给 Codex 核对配置好之后就可以把要对账的内容整理成一段清晰的提示连同两段 SQL 一起发给 Codex。建议按下面的结构组织这样 Codex 更容易逐行比对。先说明背景我在 SQL Server 里查空表和非空表两套写法结果对不上请帮我逐行核对where条件的差异。然后贴第一段也就是游标查空表的那段use 库名 go declare tablename nvarchar(100) declare sql nvarchar(2000) declare count int declare a int declare cur_c cursor for select name from sysobjects where xtypeU and status0 open cur_c fetch next from cur_c into tablename while fetch_status 0 begin set sqlselect acount(*) from tablename exec sp_executesql sql,Na int output,count output if count0 print tablename fetch next from cur_c into tablename end close cur_c deallocate cur_c再贴第二段也就是查非空表的两条select B.name from sys.partitions A inner join sys.objects B on A.object_idB.object_id where B.typeU and A.rows0 select B.name from sysindexes A inner join sys.objects B on A.idB.object_id where B.typeU And A.rows 0最后提出明确要求请逐行核对xtype、status0、A.rows0这些条件的差异指出哪张表的判定会在两种写法下打架并给出一个统一的空表/非空表查询写法。把这段提示发给 Codex 后它会从几个角度帮你拆解。第一是对象范围是否一致游标用的是sysobjects加xtypeU and status0而sys.partitions、sysindexes那两条用的是sys.objects加B.typeU。sysobjects是兼容视图sys.objects是新的目录视图两者在对象可见性、系统对象过滤上存在细微差别status0这个条件在sys.objects里并没有对应写法这就会导致两边扫描到的表集合本身可能就不完全相同。第二是行数来源是否一致游标是实时count(*)sys.partitions.rows是分区级行数统计sysindexes.rows是索引级行数统计。对于有聚集索引的表sys.partitions里index_id为 0 或 1 的那一行才代表表本身的行数如果直接对sys.partitions不加index_id过滤一张表可能因为多个分区或多条索引记录而出现多行A.rows0的判定就会变得含糊。sysindexes同理它按索引记录行数indid不同含义也不同。第三是“空”和“非空”的边界是否互补游标打印的是count(*)0的表也就是空表sys.partitions、sysindexes查的是rows0的表也就是非空表。理论上两者应该刚好互补但因为上面说的对象范围和行数来源都不一致实际结果就会出现交集和缺口。Codex 在核对时会把这些差异逐条列出来并指出哪些表最可能在两种写法下“打架”。比如一张表在sysobjects里可见、在sys.objects里也可见但它的sys.partitions.rows因为统计信息滞后还是旧值那么它就会同时出现在空表清单和非空表清单里。反过来如果一张表在sys.objects里可见但被status0过滤掉了它就可能只在一边出现。四、验证请求与成功结果统一写法怎么落地Codex 给出的统一写法核心思路是让两边用同一套对象范围和同一套行数来源。最稳妥的做法是对象范围统一用sys.objects加typeU行数统一用实时count(*)或者统一用sys.partitions并明确index_id过滤条件。如果追求结果绝对准确建议统一走实时count(*)。可以写成一个动态 SQL 批量统计或者沿用游标思路但把对象来源换成sys.objectsuse 库名 go declare tablename nvarchar(100) declare sql nvarchar(2000) declare count int declare a int declare cur_c cursor for select name from sys.objects where typeU open cur_c fetch next from cur_c into tablename while fetch_status 0 begin set sqlselect acount(*) from QUOTENAME(tablename) exec sp_executesql sql,Na int output,count output if count0 print 空表: tablename else print 非空表: tablename fetch next from cur_c into tablename end close cur_c deallocate cur_c这样一段就能同时输出空表和非空表而且判定口径完全一致不会再出现两边对不上的情况。注意这里用了QUOTENAME包裹表名避免表名里有特殊字符时拼 SQL 出错。如果表数量很大实时count(*)开销高想用统计信息快速估算那就统一走sys.partitions并且明确只取表本身的那一行select B.name, case when A.rows 0 then 非空表 else 空表 end as 状态 from sys.partitions A inner join sys.objects B on A.object_id B.object_id where B.type U and A.index_id in (0, 1)这里index_id in (0, 1)是为了只取堆表或聚集索引对应的那一行避免一张表因为多条索引记录而重复出现。用这一套写法空表和非空表来自同一个数据源逻辑上天然互补不会再打架。把 Codex 给出的改法落回原文脚本后建议在本地 SQL Server 里实际跑一遍验证。先跑统一写法把结果集存下来再分别跑原来的游标写法和sys.partitions、sysindexes写法对比差异。如果统一写法的结果和游标实时统计一致说明口径已经对齐如果和sys.partitions估算有出入那属于统计信息滞后可以按需更新统计信息后再看。五、本篇常见错排查在对账和改写过程中有几个高频错误值得单独拎出来说。第一个是sys.partitions不加index_id过滤。很多人直接写where B.typeU and A.rows0结果一张表因为有多条索引记录而被重复列出或者因为某条非聚集索引的rows为 0 而误判。正确做法是加上A.index_id in (0, 1)只取表本身的行数记录。第二个是sysindexes的rows语义混淆。sysindexes是旧版兼容视图rows字段在不同indid下含义不同直接拿它和count(*)对比很容易对不上。如果只是做快速估算建议优先用sys.partitions如果要精确结果就用实时count(*)。第三个是sysobjects和sys.objects混用。sysobjects里的xtypeU和sys.objects里的typeU虽然都表示用户表但两个视图的可见范围和字段语义有差异status0这种条件在sys.objects里没有直接对应。统一写法时对象来源要么全用sysobjects要么全用sys.objects不要一边一个。第四个是动态 SQL 拼表名没做转义。游标里拼sql时如果表名包含空格、连字符或保留字不加QUOTENAME就会报语法错误。统一写法里记得用QUOTENAME(tablename)包一层。第五个是统计信息滞后导致的误判。即使写法统一了sys.partitions.rows仍可能和实时count(*)有偏差。如果对结果准确性要求高可以在查询前对相关表执行UPDATE STATISTICS或者干脆用实时count(*)。第六个是权限问题。查sys.partitions、sysindexes、sys.objects需要相应的目录视图权限如果当前登录账号权限不足可能看不到全部表导致结果集偏小。排查时可以先确认当前账号能看到的对象范围。六、语义一致 CTA对账做到这里核心动作其实就三步把两段 SQL 一起交给 Codex 逐行核对让它指出xtype、status0、A.rows0这些条件的差异再拿一个统一的空表/非空表写法回本地验证。TaoToken 在整个过程里只做一件事就是给 Codex 供 Key 和 Base URLSQL 始终在你自己的 SQL Server 里跑。如果你还在配置接入阶段先去 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 注册并创建 Key然后到 API Keys 页面确认 Key 状态正常再对照接入文档把 Base URL 填成https://taotoken.net/api。接入过程中如果遇到报错优先检查 Key 是否填对、Base URL 是否有多余路径、模型 ID 是否和控制台一致。如果你更习惯在对话界面里边聊边核对 SQL可以直接打开模型对话把两段 SQL 贴进去让 Codex 帮你逐行比对。如果你打算把这种对账动作长期做下去比如定期巡检数据库空表、非空表或者把 Codex 接进日常的编码和脚本维护流程可以了解一下 Coding Plan把这类重复性的核对工作固定下来。需要管理多个 Key 或查看调用情况时控制台和 API Keys 页面都能直接操作。