varchar,nvarchar不同呀。小心出错

本文记录了一次因varchar和nvarchar处理中文字符不一致导致的数据库主键冲突问题。通过对比不同类型的字符串变量,展示了乱码出现的原因及如何避免此类问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今天调程序,执行SQL老是报错。最终发现是 varchar 和 nvarchar 造成的。郁闷。
本来没有这个问题,但是接口使用方传过来的字串编码错误,导致乱码。
None.gifCREATE TABLE [TEST] (
None.gif    
[nn] [varchar] (50) COLLATE Chinese_PRC_CI_AS NOT NULL ,
None.gif    
CONSTRAINT [PK_TEST] PRIMARY KEY  CLUSTERED 
None.gif    (
None.gif        
[nn]
None.gif    )  
ON [PRIMARY] 
None.gif
ON [PRIMARY]
None.gif
GO
None.gif
None.gif
select * from TEST
None.gif
None.gif
insert into TEST(nn)
None.gif
values(N'BSS?ê?§ID')
None.gif
None.gif
insert into TEST(nn)
None.gif
values(N'BSSÕÊ»§ID')
ExpandedBlockStart.gifContractedBlock.gif
/**//*
InBlock.gif服务器: 消息 2627,级别 14,状态 1,行 1
InBlock.gif违反了 PRIMARY KEY 约束 'PK_TEST'。不能在对象 'TEST' 中插入重复键。
InBlock.gif语句已终止。
InBlock.gif
ExpandedBlockEnd.gif
*/

None.gif
None.gif
declare @s nvarchar(50)
None.gif    ,
@s2 nvarchar(50)
None.gif
None.gif
select @s =N'BSS?ê?§ID' , @s2 = N'BSSÕÊ»§ID'
None.gif
None.gif
select 1 where @s=@s2
None.gif
None.gif
select 1 where N'BSS?ê?§ID'=N'BSSÕÊ»§ID'
ExpandedBlockStart.gifContractedBlock.gif
/**//*
InBlock.gif
InBlock.gif(所影响的行数为 0 行)
InBlock.gif
InBlock.gif
InBlock.gif(所影响的行数为 0 行)
ExpandedBlockEnd.gif
*/

None.gif
None.gif
declare @s varchar(50)
None.gif    ,
@s2 varchar(50)
None.gif
None.gif
select @s =N'BSS?ê?§ID' , @s2 = N'BSSÕÊ»§ID'
None.gif
None.gif
select 1 where @s=@s2
None.gif
None.gif
select 1 where N'BSS?ê?§ID'=N'BSSÕÊ»§ID'
ExpandedBlockStart.gifContractedBlock.gif
/**//*
InBlock.gif
InBlock.gif(所影响的行数为 1 行)
InBlock.gif
InBlock.gif
InBlock.gif(所影响的行数为 0 行)
InBlock.gif
ExpandedBlockEnd.gif
*/

转载于:https://www.cnblogs.com/evlon/archive/2006/05/25/409049.html

EXEC SP_HCP請假資料作廢 'Pre250218143','T00024','2025-02-03 08:00:00','2025-02-07 17:00:00','L26' -- EXEC HCPSalary.dbo.SP_HCP請假資料作廢 'Pre240903202', 'U173276', '2024/08/29 08:00', '2024/09/11 17:00' , 'L10' ALTER PROC [dbo].[SP_HCP請假資料作廢] @P_SERNO VARCHAR(20), --請假單號 @P_ID_NO_SZ varchar(10), --員工代號(必填) @P_DATETIME_B varchar(20), --請假日期時間(起)格式西元YYYY/MM/DD HH24:MI (必填) @P_DATETIME_E varchar(20), --請假日期時間(迄)格式西元YYYY/MM/DD HH24:MI (必填) @P_REASON varchar(5) -- 假別 (必填) AS BEGIN DECLARE @P_SEGMENT_NO VARCHAR(10)='9999', --公司id(必填) @P_ERROR_TYPE VARCHAR(4)='MSG' --訊息類別 'MSG'多語訊息 'CODE'代碼 'FULL' 代碼加訊息 預設為多語訊息 DECLARE @P_ERROR VARCHAR(1000)='' --錯誤原因 ------------ 向HCP資料庫中INSERT資料 Print '@P_SERNO的值是:' + @P_SERNO Print '@P_ID_NO_SZ的值是:' + @P_ID_NO_SZ Print '@P_DATETIME_B的值是:' + @P_DATETIME_B Print '@P_DATETIME_E的值是:' + @P_DATETIME_E Print '@P_REASON的值是:' + @P_REASON Print '@P_SEGMENT_NO的值是:' + @P_SEGMENT_NO Print '@P_ERROR_TYPE的值是:' + @P_ERROR_TYPE DECLARE @P_DATETIME_B_1 VARCHAR(30) = CONVERT(VARCHAR, @P_DATETIME_B, 120) DECLARE @P_DATETIME_E_1 VARCHAR(30) = CONVERT(VARCHAR, @P_DATETIME_E, 120) Print '@P_DATETIME_B_1:' + @P_DATETIME_B_1 Print '@P_DATETIME_E_1:' + @P_DATETIME_E_1 DECLARE @P_DATETIME_B_2 DATETIME = CAST(@P_DATETIME_B_1 AS DATETIME) DECLARE @P_DATETIME_E_2 DATETIME = CAST(@P_DATETIME_E_1 AS DATETIME) --Print '@P_DATETIME_B_2:' + @P_DATETIME_B_2 --Print '@P_DATETIME_E_2:' + @P_DATETIME_E_2 EXECUTE ('BEGIN hcp.P_ABSENCE_DELETE_TW(?,?,?,?,?,?,?,?); END;', @P_SERNO, @P_ID_NO_SZ, @P_DATETIME_B_2, @P_DATETIME_E_2, @P_REASON, @P_SEGMENT_NO, @P_ERROR output, @P_ERROR_TYPE ) AT SAL_HCPPROD Print @P_ERROR END@P_SERNO的值是:Pre250218143 @P_ID_NO_SZ的值是:T00024 @P_DATETIME_B的值是:2025-02-03 08:00:00 @P_DATETIME_E的值是:2025-02-07 17:00:00 @P_REASON的值是:L26 @P_SEGMENT_NO的值是:9999 @P_ERROR_TYPE的值是:MSG @P_DATETIME_B_1:2025-02-03 08:00:00 @P_DATETIME_E_1:2025-02-07 17:00:00 OLE DB provider "OraOLEDB.Oracle" for linked server "SAL_HCPPROD" returned message "ORA-06550: line 1, column 7: PLS-00306: wrong number or types of arguments in call to 'P_ABSENCE_DELETE_TW' ORA-06550: line 1, column 7: PL/SQL: Statement ignored". 訊息 7215,層級 17,狀態 1,程序 SP_HCP請假資料作廢,行 42 [批次開始行 0] Could not execute statement on remote server 'SAL_HCPPROD'. 完成時間: 2025-06-06T16:35:03.2292607+08:00
06-07
<think>我们正在讨论varcharnvarchar之间的转换规则以及转换时可能发生的长度变化。关键点:1.存储方式不同varchar存储的是非Unicode字符,每个字符占用1个或多个字节(取决于编码);nvarchar存储的是Unicode字符(通常是UTF-16),每个字符固定占用2个字节(对于基本多语言平面字符)。2.转换规则:-当将varchar转换为nvarchar时,由于每个字符可能变成两个字节(如果是ASCII字符,则原来占1个字节,现在占2个字节;如果是多字节字符,则原来可能占2-4个字节,现在统一占2个字节,但注意:在nvarchar中,补充平面的字符会占用4个字节,即两个代理字符)。-当将nvarchar转换为varchar时,如果目标字符集不包含源字符串中的某些字符,则可能出现数据丢失(无法表示的字符会被替换为问号或其他默认字符),同时长度可能发生变化(每个Unicode字符在非Unicode编码中可能占用1个或多个字节,也可能无法表示)。具体转换时的长度变化:-varcharnvarchar:由于每个字符在nvarchar中至少占用2个字节(基本多语言平面的字符),所以转换后的字符串所占的字节数通常会增加(除非原字符串为空)。但注意,字符数(即字符串长度)不变。-nvarcharvarchar:每个字符在varchar中占用的字节数取决于目标列的字符编码和具体字符。例如,在GBK编码中,一个中文字符占2个字节,而一个ASCII字符占1个字节。因此,转换后的字节长度可能会减少(如果原字符串中有很多ASCII字符)也可能增加(如果原字符串中有很多需要多字节表示的字符)?实际上,由于nvarchar中每个字符都是2字节(基本多语言平面),而转为varchar后,对于非ASCII字符,在单字节编码(如Latin1)中无法表示,在多字节编码(如GBK、UTF-8)中可能占用多个字节。所以,转换后的字节长度变化不确定,但字符数可能减少(因为可能有些字符无法转换而被截断或替换,导致实际存储的字符数减少)。另外,在SQLServer中,当进行数据类型转换时,需要注意:-如果目标类型的长度不足以容纳转换后的字符串,则会发生截断(并可能报错,取决于ANSI_WARNINGS设置)。-在转换时,可能需要使用CONVERT或CAST函数,并且可以指定样式。根据引用[4]:在Unicode中,一个字符就是两个字节(基本多语言平面)。但要注意,在varchar中,一个中文字符在GBK编码下是2个字节,在UTF-8编码下是3个字节。因此,转换规则总结如下:1.varcharnvarchar:字符数不变,但存储空间通常增加(因为每个字符从1个或多个字节变成2个或4个字节)。例如:-原varchar字段存储'abc'(3个字节),转成nvarchar后,变成6个字节(每个字符2字节)。-原varchar字段存储'你好'(在GBK中是4个字节,在UTF-8中是6个字节),转成nvarchar后,变成4个字节(两个字符,每个2字节)?注意:在nvarchar中,中文字符属于基本多语言平面,每个字符2字节,所以两个字符共4字节。因此,转换后的字节长度=原字符串的字符数*2(如果都是基本多语言平面的字符)。2.nvarcharvarchar:转换后的字节长度取决于目标编码和原字符串的字符。-如果原nvarchar字符串是'abc'(3个字符,6个字节),转为varchar(假设编码是Latin1,只能存ASCII),则转换后为'abc',占3个字节。-如果原nvarchar字符串是'你好'(2个字符,4个字节),转为varchar(使用GBK编码)后,每个中文字符占2个字节,共4个字节;如果使用UTF-8编码,则每个中文字符占3个字节,共6个字节。-如果原nvarchar字符串包含Latin1编码无法表示的字符(比如中文字符),则转换时这些字符会被替换为默认字符(如问号),导致数据丢失。另外,由于目标varchar列有长度限制(比如定义为varchar(10)),那么转换后的字符串字节数不能超过10。如果原nvarchar字符串有5个字符(在nvarchar中占10字节),转为varchar(UTF-8)后,如果这5个字符都是中文,则需要15个字节,超过10则会被截断。在SQLServer中,进行显式转换时,需要注意目标类型的长度。例如:CAST(nvarchar_columnASvarchar(100))这里指定了目标类型为varchar(100),表示最多100个字节(注意不是字符)。如果转换后的字节数超过100,则会被截断。自动转换(隐式转换)也可能发生,但通常由数据库引擎根据上下文进行,同样要注意长度问题。建议:-在转换时,务必考虑目标类型的长度是否足够,避免截断。-使用CONVERT函数时,可以指定样式来处理日期等特殊格式,但字符串转换一般不需要样式。引用说明:[^4]提到:在Unicode中,一个字符是两个字节。这指的是基本多语言平面的字符。因此,nvarchar存储这类字符时,每个字符固定2字节。而varchar则依赖于编码。另外,引用[2]提到:varchar如果超过一定长度(如大于255),可能会被转成text类型(在旧版本的MySQL中),但在SQLServer中,varchar最大可以到8000字节(SQLServer2005及以后版本还可以使用varchar(max))。因此,在转换时,如果目标类型是varchar,而源nvarchar字符串的字符数较多,则目标varchar的长度可能需要设置得更大(因为一个nvarchar字符可能转换成多个字节)。示例计算:假设有一个nvarchar(100)的列,存储了100个中文字符(在nvarchar中占200字节)。如果转为varchar,且数据库使用UTF-8编码,则每个中文字符占3字节,共300字节。因此,目标varchar的长度至少需要300(即varchar(300))才能避免截断。总结:-varcharnvarchar:字符数不变,字节数通常增加(每个字符至少变为2字节)。-nvarcharvarchar:字符数可能减少(因为无法转换的字符被替换),字节数取决于目标编码,可能增加也可能减少(但通常对于非ASCII字符,在UTF-8编码下字节数会增加)。在数据库设计时,如果需要存储多语言字符,建议使用nvarchar以避免转换问题。</think>### varcharnvarchar 相互转换的规则及长度变化 在数据库中进行 `varchar` 和 `nvarchar` 类型转换时,长度变化主要由字符编码差异引起。以下是核心规则和变化分析: --- #### 🔠 基础差异对比 | **特性** | `varchar` | `nvarchar` | |----------------|-------------------------------|-------------------------------| | **编码** | 非 Unicode (如 GBK, UTF-8) | Unicode (UTF-16) | | **存储单位** | 字节 (Byte) | 字符 (Character) | | **中文字符** | 通常占 2-4 字节 (视编码而定) | 固定占 2 字节 (基本多语言平面)[^4] | | **英文字符** | 固定占 1 字节 | 固定占 2 字节 | --- #### 🔄 转换规则与长度变化 ##### 1. **varcharnvarchar 转换** - **长度变化**:存储空间通常**增加** - 英文字符:`1字节 → 2字节` (长度翻倍) - 中文字符:`2-4字节 → 2字节` (长度可能减小) - **示例计算**: ```sql -- 原始 varchar(10) 存储 "中国ABC" (GBK编码) -- 长度 = 2汉字×2字节 + 3字母×1字节 = 7字节 -- 转为 nvarchar(10) 后: -- 长度 = 5字符×2字节 = 10字节 ``` - **注意事项**: - 字符数不变,但字节占用增加 - 需确保目标列长度足够(如 `varchar(10)` → `nvarchar(20)`) ##### 2. **nvarcharvarchar 转换** - **长度变化**:存储空间可能**减少**,但存在风险 - 英文字符:`2字节 → 1字节` (长度减半) - 中文字符:`2字节 → 2-4字节` (长度可能增加) - **风险点**: - **数据截断**:如果目标 `varchar` 长度不足 ```sql -- nvarchar(10) 存储 "你好世界" (8字节) -- 转为 varchar(5) (GBK) 时: -- 需要 4汉字×2字节 = 8字节 → 超出长度 → 截断! ``` - **字符丢失**:非 Unicode 编码无法表示的字符会被替换为 `?` ```sql -- 包含 emoji 的 nvarchar: '你好👍' -- 转为 varchar (GBK) 后:'你好??' ``` - **安全转换建议**: ```sql -- 先计算最大可能长度 DECLARE @max_len INT = ( SELECT MAX(LEN(CONVERT(varchar(MAX), nvarchar_col))) FROM table ); -- 动态调整目标长度 ALTER TABLE table ALTER COLUMN col varchar(@max_len); ``` --- #### ⚠️ 关键注意事项 1. **编码一致性**: - 转换结果依赖数据库的默认字符集(如 SQL Server 的排序规则) - 示例:`nvarchar` 转 `varchar` 在 `Chinese_PRC_CI_AS` 与 `Latin1_General_CI_AS` 结果不同 2. **隐式转换风险**: - 混合操作时(如 `varchar + nvarchar`),数据库会优先转成 `nvarchar` ```sql SELECT '中' + N'ABC' -- 隐式将 '中' 转为 nvarchar -- 结果长度 = 4字符×2字节 = 8字节 ``` 3. **性能影响**: - 大字段转换可能引发全表扫描 - 建议在低峰期操作,或分批次处理 --- #### 💡 最佳实践 1. **设计阶段**: - 多语言系统优先使用 `nvarchar` - 纯英文系统用 `varchar` 节省空间 2. **转换操作**: ```sql -- SQL Server 显式安全转换 SELECT nvarchar_col AS original, CONVERT(varchar(100), nvarchar_col) AS converted, DATALENGTH(CONVERT(varchar(100), nvarchar_col)) AS new_length FROM table WHERE LEN(nvarchar_col) <= 50; -- 长度过滤防截断 ``` 3. **长度验证**: ```sql -- 检查转换后是否可能截断 SELECT * FROM table WHERE LEN(nvarchar_col) * 2 > @target_varchar_length; -- UTF-8 最坏情况 ``` --- #### 📚 相关问题 1. 如何避免 `nvarchar` 转 `varchar` 时的字符丢失问题? 2. 在 UTF-8 编码的数据库中,`varchar` 存储中文字符占用几个字节? 3. 如何计算 `nvarchar` 字段转换为 `varchar` 后的最小所需长度? 4. SQL Server 中排序规则如何影响字符转换结果?[^1][^3] 5. 大字段类型转换有哪些性能优化方法?[^2]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值