SQL Server中如何检测nvarchar字段包含指定范围外的字符?
你遇到的问题核心在于:你用N'...'定义的是Unicode字符串(nvarchar类型),但指定的SQL_Latin1_General_Cp1_CS_AS是非Unicode(单字节)排序规则,这两种类型组合在一起时,SQL Server的字符匹配逻辑会出现偏差,导致结果不符合预期。
拆解第一个示例的问题
你的查询:
select foo from ( select N'ABDC' as foo union all select N'ABǼC' as foo ) as bar where bar.foo like '%[^ -~]%' COLLATE SQL_Latin1_General_Cp1_CS_AS
之所以返回两行(包括原本正常的ABDC),是因为当非Unicode排序规则处理Unicode字符串时,SQL Server会尝试把Unicode字符转换为对应的单字节字符。对于Ǽ这种没有单字节映射的字符,会被替换成一个替代字符(通常是?),而?不在[ -~]的ASCII范围内。更关键的是,非Unicode排序规则对Unicode字符串的范围匹配逻辑并不精准,导致原本全ASCII的ABDC也被误判为包含无效字符。
拆解第二个示例的问题
再看这个查询:
select foo from ( select N'ABDC' as foo union all select N'ABzC' as foo ) as bar where bar.foo like '%[^A-Z]%' COLLATE SQL_Latin1_General_Cp1_CS_AS
它没有返回包含小写z的ABzC,是因为非Unicode排序规则处理Unicode的z时,大小写区分的逻辑和预期不一致——它并没有正确识别z是小写字母,不在A-Z的范围内。而当你用~测试时,~是ASCII字符,非Unicode排序规则能正确处理,所以结果符合预期。
正确的解决方案
针对nvarchar类型的字段,必须使用支持Unicode的排序规则(名称通常包含_100_、_SC后缀,代表支持补充字符),比如Latin1_General_100_CS_AS_SC。
修复第一个示例(检测ASCII范围外字符)
select foo from ( select N'ABDC' as foo union all select N'ABǼC' as foo ) as bar where bar.foo like '%[^ -~]%' COLLATE Latin1_General_100_CS_AS_SC
这个查询会精准匹配出包含Ǽ的那一行,而ABDC会被正确排除。
修复第二个示例(检测非大写字母)
select foo from ( select N'ABDC' as foo union all select N'ABzC' as foo ) as bar where bar.foo like '%[^A-Z]%' COLLATE Latin1_General_100_CS_AS_SC
现在会正确返回包含小写z的ABzC行,因为Unicode排序规则能准确区分大小写字母的范围。
另一种更直观的方法:用ASCII函数判断
如果你想彻底避免排序规则的坑,还可以直接检查每个字符的ASCII码值:
select foo from ( select N'ABDC' as foo union all select N'ABǼC' as foo ) as bar where EXISTS ( SELECT 1 FROM STRING_SPLIT(bar.foo, '') WHERE ASCII(value) NOT BETWEEN 32 AND 126 -- 32是空格,126是~的ASCII码 )
这种方法直接验证每个字符是否在ASCII空格到波浪线的范围内,逻辑更清晰,也不会受排序规则影响。
内容的提问来源于stack exchange,提问作者Yuyo

