SQL Server意外'按顺序排列' varchar与nvarchar的区别

时间:2017-02-22 20:07:10

标签: sql-server

我在'排序'的结果之间遇到了意想不到的差异。使用varchar与nvarchar数据。在这两种情况下,所讨论的数据都来自旧的ASCII字符集;在以nnn vs -nnn开头的数据排序中出现差异,其中n是数字。

下面是重现问题的SQL Server脚本;我的测试服务器是SQL 2016,但我也在2008年和2012年重现了这个问题。我尝试了不同的排序规则而没有效果(Latin1_General_bin除外,见下文)。该脚本创建了两个类似于我们的应用程序中的样本表,一个使用varchar,另一个使用nvarchar,并添加了7行数据。

CREATE TABLE [dbo].[_ValidationLists](
    [_FldNum] [int] NOT NULL,
    [_ValidationEntry] [varchar](250) NOT NULL,
 CONSTRAINT [PK__ValidationLists] PRIMARY KEY CLUSTERED 
(
    [_ValidationEntry] ASC,
    [_FldNum] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

CREATE TABLE [dbo].[_ValidationListsN](
    [_FldNum] [int] NOT NULL,
    [_ValidationEntry] [nvarchar](250) NOT NULL,
 CONSTRAINT [PK__ValidationListsN] PRIMARY KEY CLUSTERED 
(
    [_ValidationEntry] ASC,
    [_FldNum] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

INSERT INTO [_ValidationLists] (_fldnum, _ValidationEntry) VALUES (1,'-1'), (1,'-10'), (1,'-100'), (1,'0'), (1,'1'), (1,'10'), (1,'100')
INSERT INTO [_ValidationListsN] (_fldnum, _ValidationEntry) VALUES (1,N'-1'), (1,N'-10'), (1,N'-100'), (1,N'0'), (1,N'1'), (1,N'10'), (1,N'100')


select * from [_ValidationLists]
order by [_ValidationEntry] asc
select * from [_ValidationListsN]
order by [_ValidationEntry] asc

select语句的结果如下。 varchar的第一个结果是我所期望的(词典排序);第二个结果我无法解释。第一个是我们的客户群所期望的,我们被这个结果感到惊讶。 (客户数据不常见 - 通常此表用于alpha数据;而且对于varchar和nvarchar,alpha数据顺序相同。)

结果是相同的使用N' ...'初始化_ValidationListsN行。原始数据的条目较长,例如' -100:Pass&#39 ;;我已将数据编辑到最少,这证明了问题。

使用空白进行右边填充,因此所有条目长度相同都没有效果。

使用COLLATE Latin1_General_bin重现词典排序,但是不可接受,因为(仅有一个原因)我们通常使用不区分大小写的排序。

我们报告此问题的客户只有ASCII数据,因此我们可以通过使用varchar重新创建此表来修复它们。我很想知道为什么nvarchar会以这种方式运行,因为结果对我来说似乎不正确,并且如果有办法获得我们期望的排序行为(第一种情况)。至少我不知道为什么所有条目都以' - '开头。 (ASCII 0X2d,短划线或减号)不要一起订购。

_FldNum _ValidationEntry

1           -1  
1           -10  
1           -100  
1           0  
1           1  
1           10  
1           100  

(7行(s)受影响)

_FldNum _ValidationEntry

1           0  
1           1  
1           -1  
1           10  
1           -10  
1           100  
1           -100  

(7行(s)受影响)

1 个答案:

答案 0 :(得分:0)

当SQL Server解析ORDER BY时,它不仅从排序规则决定行的顺序,而且如您已经发现的那样,从数据类型决定行的顺序。

varchar和nchar最后只是二进制文件。问题是,在varchar中,“ - ”符号出现在数字/字符之后(当涉及二进制表示法时),对于nvarchar,它是相反的。检查互联网上的ASCI / UNICODE表。

正因为如此,由于ORDER BY最终会比较二进制,所以你的负值首先是nvarchar。

如果您对如何实际存储数据更感兴趣,请预订“Microsoft SQL Server Internals”可能对您有意义。有一整节讨论这个问题。

编辑:

要了解数据如何实际存储在db中,请参阅以下代码段:

SELECT 
   [_ValidationEntry], 
   CONVERT(binary(6), [_ValidationEntry]) AS [BinaryRepresentation] 
FROM [_ValidationLists]
--ORDER BY [_ValidationEntry] COLLATE Latin1_General_bin

SELECT 
   [_ValidationEntry], 
   CONVERT(binary(6), [_ValidationEntry]) AS [BinaryRepresentation] 
FROM [_ValidationListsN]
--ORDER BY [_ValidationEntry] COLLATE Latin1_General_bin

查询结果如下:

0      0x300000000000
1      0x310000000000
-1     0x2D3100000000
10     0x313000000000
-10    0x2D3130000000
100    0x313030000000
-100   0x2D3130300000

使用BIN归类时,列按其二进制表示排序,因此,2D减号首先出现。