1. 为什么 varchar 转 nvarchar 不能直接一把梭
先说清楚这件事到底在解决什么问题。SQL Server 里varchar是单字节存储,nvarchar是 Unicode 双字节存储。当你的库早期设计时只考虑了英文和数字,后来业务开始录入中文、日文、emoji 或者生僻字,就会出现「存进去是问号」「查询匹配不上」「导出乱码」这类问题。这时候最直接的办法就是把相关字段从varchar改成nvarchar。
但麻烦在于,一个跑了三五年的库,几十上百张表,每张表里可能有三五个varchar字段,手工一张张改根本不现实。你需要的是:查出所有varchar字段,自动生成ALTER TABLE ... ALTER COLUMN语句,然后批量执行。
听起来简单,实际动手会撞上几个坑:
第一,ALTER COLUMN改类型时,如果该字段有索引、有默认值约束、有 CHECK 约束,SQL Server 会直接报错拒绝执行。你得先把依赖拆掉,改完再建回去。
第二,nvarchar的长度参数和varchar不一样。varchar(50)改成nvarchar(50)是安全的,但如果你想把varchar(max)改成nvarchar(max),语法上要写nvarchar(max)而不是nvarchar(MAX)的大小写问题(其实 SQL Server 不区分,但脚本生成时容易拼错)。
第三,系统视图sys.columns里的max_length是字节数。对于varchar(50),max_length返回 50;但对于nvarchar(50),max_length返回 100。所以你在判断和拼接时,如果直接拿max_length去拼nvarchar(...),长度会翻倍。正确做法是用sys.types里的信息或者INFORMATION_SCHEMA.COLUMNS的CHARACTER_MAXIMUM_LENGTH。
第四,改字段类型可能触发数据截断。比如原来varchar(10)存了 10 个英文字符,改成nvarchar(10)后能存 10 个 Unicode 字符,没问题;但如果反过来或者长度计算错误,就可能丢数据。执行前必须备份。
我试过在一个测试库上直接跑网上抄来的游标脚本,结果因为一张表的主键索引依赖被卡住,整个游标中断,只改了一半。后来才学会先查依赖、先生成脚本、人工审核后再执行。
这一篇就围绕「SQL Server 批量把指定数据库所有表的 varchar 字段改为 nvarchar」这个场景,交付三样东西:可复制的游标+动态 SQL 脚本、用 TaoToken 辅助生成和审查脚本的提示词模板、以及执行前后用INFORMATION_SCHEMA对比验证的完整动作。你跟着做就能在自己的测试库上跑通。
2. 用 TaoToken 辅助生成与审查批量改类型脚本
批量改字段类型这种任务,脚本逻辑不复杂但细节多,很容易漏掉依赖检查或者长度换算。我的做法是先用 TaoToken 把脚本框架和边界条件过一遍,再拿到 SSMS 里执行。
TaoToken 在这里的角色是「脚本生成 + 代码审查助手」。你可以把系统视图的查询逻辑、游标结构、动态 SQL 拼接规则描述给它,让它输出一版可读的 T-SQL,然后你再针对自己的库做调整。它也能帮你检查「max_length在varchar和nvarchar下的差异」「索引依赖怎么查」这类容易搞错的地方。
先拿 API Key。打开 https://taotoken.net/api-keys ,创建一个 Key,复制出来。这个 Key 后面在调用模型对话接口时要用。
如果你只是想先验证模型能不能正确理解 T-SQL 游标和系统视图,可以直接进模型对话页面 https://taotoken.net/model-chat ,把下面这段提示词贴进去:
你是 SQL Server DBA。请帮我写一段 T-SQL 脚本,功能是: 1. 查询指定数据库(比如 TestDB)中所有用户表(type='U')里数据类型为 varchar 的字段; 2. 用游标遍历这些字段,动态拼接 ALTER TABLE ... ALTER COLUMN ... nvarchar(n) 语句; 3. 注意 sys.columns.max_length 对 varchar 是字节数,对 nvarchar 会翻倍,拼接 nvarchar 长度时要用正确值; 4. 输出生成的 ALTER 语句,先不执行,让我人工审核。 请给出完整脚本,并说明哪些系统视图字段用于判断类型和长度。模型返回的脚本你可以直接对照后面的章节使用。如果你要长期做数据库脚本生成和审查,可以考虑 Coding Plan https://taotoken.net/coding-plan ,把常用提示词固化下来,每次改库前跑一遍。
拿到 Key 之后,如果你习惯在命令行里调 API,可以这样验证连通性:
curl https://taotoken.net/api/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer 你的API_KEY" \ -d '{ "model": "claude-sonnet-4-20250514", "messages": [ {"role": "user", "content": "用一句话说明 SQL Server 中 varchar 和 nvarchar 的核心区别"} ] }'返回里能看到choices[0].message.content就说明 Key 和接口都正常。注意 Base URL 是https://taotoken.net/api,不要多加/v1之外的路径。模型 ID 按你实际可用的填,上面只是示例。
这一步的目的不是让模型替你执行,而是让它帮你把脚本里的坑提前标出来。比如它会提醒你:ALTER COLUMN不能在有索引的列上直接改,需要先DROP INDEX;有默认值约束的列要先DROP CONSTRAINT。这些提醒能省掉你至少一轮报错排查。
3. 可复制的游标 + 动态 SQL 配置与脚本
这一节给出完整可执行的 T-SQL。分三步:先查依赖,再生成脚本,最后执行。不要跳过第一步。
3.1 查询所有 varchar 字段及其依赖
先连到目标数据库,比如TestDB,然后跑这段查询,看看有哪些varchar字段:
USE TestDB; GO SELECT t.name AS table_name, c.name AS column_name, ty.name AS type_name, c.max_length, c.is_nullable FROM sys.tables t INNER JOIN sys.columns c ON t.object_id = c.object_id INNER JOIN sys.types ty ON c.user_type_id = ty.user_type_id WHERE t.type = 'U' AND ty.name = 'varchar' ORDER BY t.name, c.column_id;max_length这里对varchar(50)返回 50,对varchar(max)返回 -1。记住这个值,后面拼接nvarchar长度时要用INFORMATION_SCHEMA.COLUMNS.CHARACTER_MAXIMUM_LENGTH来换算,或者直接用max_length但判断-1的情况。
查索引依赖:
SELECT t.name AS table_name, c.name AS column_name, i.name AS index_name, i.type_desc FROM sys.indexes i INNER JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id INNER JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id INNER JOIN sys.tables t ON i.object_id = t.object_id INNER JOIN sys.types ty ON c.user_type_id = ty.user_type_id WHERE ty.name = 'varchar' AND t.type = 'U' ORDER BY t.name, c.name;查默认值约束:
SELECT t.name AS table_name, c.name AS column_name, dc.name AS constraint_name, dc.definition FROM sys.default_constraints dc INNER JOIN sys.columns c ON dc.parent_object_id = c.object_id AND dc.parent_column_id = c.column_id INNER JOIN sys.tables t ON c.object_id = t.object_id INNER JOIN sys.types ty ON c.user_type_id = ty.user_type_id WHERE ty.name = 'varchar' AND t.type = 'U';这三段查完,你心里就有数了:哪些字段能直接改,哪些要先拆索引或约束。
3.2 生成 ALTER 脚本(不执行)
下面这段游标脚本只生成语句,不执行。你可以把结果复制出来人工审核:
USE TestDB; GO DECLARE @tableName SYSNAME; DECLARE @columnName SYSNAME; DECLARE @maxLength INT; DECLARE @sql NVARCHAR(MAX); DECLARE cur CURSOR FOR SELECT t.name AS table_name, c.name AS column_name, c.max_length FROM sys.tables t INNER JOIN sys.columns c ON t.object_id = c.object_id INNER JOIN sys.types ty ON c.user_type_id = ty.user_type_id WHERE t.type = 'U' AND ty.name = 'varchar' ORDER BY t.name, c.column_id; OPEN cur; FETCH NEXT FROM cur INTO @tableName, @columnName, @maxLength; WHILE @@FETCH_STATUS = 0 BEGIN IF @maxLength = -1 SET @sql = 'ALTER TABLE [' + @tableName + '] ALTER COLUMN [' + @columnName + '] NVARCHAR(MAX);'; ELSE SET @sql = 'ALTER TABLE [' + @tableName + '] ALTER COLUMN [' + @columnName + '] NVARCHAR(' + CAST(@maxLength AS VARCHAR(10)) + ');'; PRINT @sql; FETCH NEXT FROM cur INTO @tableName, @columnName, @maxLength; END CLOSE cur; DEALLOCATE cur;把PRINT换成EXEC(@sql)就是直接执行。但建议先PRINT,把输出复制到新窗口审核一遍。
如果你用 TaoToken 生成脚本,可以让它输出带PRINT和EXEC两个版本的对比,方便你切换。提示词里加上「请分别给出只打印不执行、以及直接执行两个版本,执行版要加事务和错误捕获」。
3.3 带事务和错误捕获的执行版
审核完脚本后,用这个版本执行:
USE TestDB; GO SET XACT_ABORT ON; BEGIN TRY BEGIN TRANSACTION; DECLARE @tableName SYSNAME; DECLARE @columnName SYSNAME; DECLARE @maxLength INT; DECLARE @sql NVARCHAR(MAX); DECLARE cur CURSOR FOR SELECT t.name, c.name, c.max_length FROM sys.tables t INNER JOIN sys.columns c ON t.object_id = c.object_id INNER JOIN sys.types ty ON c.user_type_id = ty.user_type_id WHERE t.type = 'U' AND ty.name = 'varchar' ORDER BY t.name, c.column_id; OPEN cur; FETCH NEXT FROM cur INTO @tableName, @columnName, @maxLength; WHILE @@FETCH_STATUS = 0 BEGIN IF @maxLength = -1 SET @sql = 'ALTER TABLE [' + @tableName + '] ALTER COLUMN [' + @columnName + '] NVARCHAR(MAX);'; ELSE SET @sql = 'ALTER TABLE [' + @tableName + '] ALTER COLUMN [' + @columnName + '] NVARCHAR(' + CAST(@maxLength AS VARCHAR(10)) + ');'; EXEC sp_executesql @sql; FETCH NEXT FROM cur INTO @tableName, @columnName, @maxLength; END CLOSE cur; DEALLOCATE cur; COMMIT TRANSACTION; PRINT '所有 varchar 字段已改为 nvarchar。'; END TRY BEGIN CATCH IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION; PRINT '执行失败,已回滚。错误信息:' + ERROR_MESSAGE(); END CATCH;SET XACT_ABORT ON保证任何一条语句出错就整体回滚,不会改一半留一半。
3.4 用 TaoToken 审查脚本的提示词模板
把上面执行版脚本贴给 TaoToken,用这段提示词让它审查:
请审查以下 T-SQL 脚本,重点检查: 1. 游标声明和 FETCH 顺序是否正确; 2. max_length = -1 时拼接 NVARCHAR(MAX) 是否正确; 3. 事务和错误捕获是否完整; 4. 是否存在索引或默认值约束导致 ALTER COLUMN 失败的风险; 5. 有没有更简洁的写法(比如用 STRING_AGG 一次性生成所有语句)。 脚本如下: [粘贴你的脚本]模型返回的审查意见里,通常会指出「有索引的列需要先 DROP INDEX」「有默认值约束需要先 DROP CONSTRAINT」。你根据第 3.1 节的查询结果,把需要拆依赖的字段单独处理。
4. 验证请求与执行前后对比结果
改完之后必须验证。最直接的方法是用INFORMATION_SCHEMA.COLUMNS对比执行前后的字段类型。
执行前先存一份快照:
USE TestDB; GO SELECT TABLE_NAME, COLUMN_NAME, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH INTO dbo._varchar_snapshot_before FROM INFORMATION_SCHEMA.COLUMNS WHERE DATA_TYPE = 'varchar';执行完批量修改后,再查一次:
SELECT TABLE_NAME, COLUMN_NAME, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH FROM INFORMATION_SCHEMA.COLUMNS WHERE DATA_TYPE = 'nvarchar' ORDER BY TABLE_NAME, COLUMN_NAME;对比两张表,确认原来varchar的字段现在都变成了nvarchar,且长度没有异常翻倍或截断。
如果你在命令行里调 TaoToken API 做验证,可以把前后查询结果贴给模型,让它帮你比对差异:
curl https://taotoken.net/api/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer 你的API_KEY" \ -d '{ "model": "claude-sonnet-4-20250514", "messages": [ {"role": "user", "content": "以下是执行前 varchar 字段快照和执行后 nvarchar 字段快照,请比对是否有遗漏或长度异常:\n执行前:[粘贴]\n执行后:[粘贴]"} ] }'返回结果里如果模型指出某张表的某个字段没改到,你就回到第 3.1 节查一下是不是被索引或约束挡住了。
另外,改完类型后建议跑一次数据抽样查询,确认中文和特殊字符能正常存取:
USE TestDB; GO SELECT TOP 10 * FROM 你的表名 WHERE 某个原varchar字段 LIKE N'%中文%';注意LIKE前面加N前缀,表示 Unicode 字符串。如果改之前存的中文是乱码,改类型不会自动修复已有数据,只能保证新写入的数据正常。已有乱码数据需要单独清洗。
5. 常见报错排查:401、索引依赖、OAuth 与 local proxy failed
这一节列几个实际会撞到的报错和排查动作。
报错一:401 Unauthorized(调 TaoToken API 时)
如果你在命令行调 API 返回 401,先检查 Key 是否复制完整、有没有多余空格。然后确认请求头格式:
Authorization: Bearer sk-xxxxxxxx注意Bearer和 Key 之间有一个空格。Base URL 用https://taotoken.net/api,不要写成https://taotoken.net/api/v1/v1。如果还是 401,去 https://taotoken.net/api-keys 重新生成一个 Key 再试。
报错二:The index 'xxx' is dependent on column 'yyy'
这是ALTER COLUMN最常见的报错。说明该字段上有索引。解决步骤:
先查索引名(用第 3.1 节的索引依赖查询),然后:
DROP INDEX 索引名 ON 表名; -- 执行 ALTER COLUMN ALTER TABLE 表名 ALTER COLUMN 字段名 NVARCHAR(50); -- 重建索引 CREATE INDEX 索引名 ON 表名(字段名);如果索引是主键或唯一约束,需要先ALTER TABLE ... DROP CONSTRAINT,改完再ADD CONSTRAINT。
报错三:The object 'DF_xxx' is dependent on column 'yyy'
这是默认值约束。先删约束:
ALTER TABLE 表名 DROP CONSTRAINT DF_xxx; -- 改类型 ALTER TABLE 表名 ALTER COLUMN 字段名 NVARCHAR(50); -- 重建默认值 ALTER TABLE 表名 ADD CONSTRAINT DF_xxx DEFAULT ('') FOR 字段名;报错四:local proxy failed / connection refused
如果你在本地用工具调 API 时看到local proxy failed,通常是本地网络配置或工具代理设置问题。检查你的 HTTP 客户端有没有走系统代理,或者把请求直接指向https://taotoken.net/api。如果你用的是 Cline、CC Switch 这类工具,确认 Base URL 填的是https://taotoken.net/api,Key 填的是https://taotoken.net/api-keys生成的 Key,Model ID 填你实际可用的模型名。这三件套缺一不可。
报错五:OAuth 相关错误
如果你在 Claude Code 或类似工具里配置时看到 OAuth 报错,说明你走的是 OAuth 流程而不是 API Key 流程。批量脚本生成这种任务用 API Key 就够了,不需要 OAuth。去 https://taotoken.net/api-keys 拿 Key,在工具里选 API Key 认证方式。
报错六:reading choices 相关错误
调 API 返回结构里找不到choices字段,通常是请求体格式不对。检查messages数组是否合法,model字段是否填了可用模型。可以用第 2 节的 curl 示例先验证连通性。
排查完这些,你的批量改类型脚本基本就能顺利跑完了。
6. 把脚本和提示词固化成你的改库流程
批量改字段类型这件事,做一次是救火,做成流程才是省心。我的做法是:每次改库前,先用第 3.1 节的三段查询把依赖摸清楚,然后用 TaoToken 生成一版脚本并审查,接着在测试库上跑一遍验证,最后才上生产。
提示词模板可以存下来,下次改库直接复用。比如:
你是 SQL Server DBA。我要把数据库 [库名] 中所有 varchar 字段改为 nvarchar。 请帮我: 1. 生成查询所有 varchar 字段及其索引、默认值依赖的 T-SQL; 2. 生成只打印不执行的 ALTER 脚本; 3. 生成带事务和错误捕获的执行版脚本; 4. 给出执行前后用 INFORMATION_SCHEMA 对比的验证语句。 注意 max_length = -1 时用 NVARCHAR(MAX)。把这段提示词和你的库名替换进去,TaoToken 就能输出一套完整脚本。你只需要根据实际依赖做微调。
如果你要长期做数据库维护和脚本生成,Coding Plan https://taotoken.net/coding-plan 可以把这类提示词和常用脚本管理起来,不用每次重新组织。接入文档在 https://taotoken.net/doc ,里面有 API 的详细参数说明。
最后提醒一句:任何批量改类型的操作,执行前必须备份。ALTER COLUMN虽然大多数时候是元数据操作,速度快,但一旦遇到数据截断或依赖冲突,回滚成本很高。测试库先跑,生产库后跑,这是底线。