2017-02-22 20 views
2

我遇到了'order by'与varchar与nvarchar数据的结果之间的意外差异。在这两种情况下,有问题的数据来自旧的ASCII字符集;在以nnn vs -nnn开头的订货数据中出现差异,其中n是数字。SQL Server意外'order by'区别varchar与nvarchar

下面是重现问题的SQL Server脚本;我的测试服务器是SQL 2016,但我在2008年和2012年也重现了这个问题。我尝试了不同的排序规则,但没有任何效果(除Latin1_General_bin外,请参见下文)。该脚本创建了2个类似于我们应用程序中的示例表,其中一个使用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数据;而alpha数据对于varchar和nvarchar都有相同的顺序)。

使用N'...'来初始化_ValidationListsN行的结果是相同的。原始数据的条目较长,如'-100:Pass';我已经编辑了数据,至少证明了这个问题。

用空格填充所以所有条目都是相同的长度没有影响。

使用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))

+0

如果数据是数字,为什么不使用数字数据类型? – HLGEM

+0

数据通常是alphnumeric - 在实际的客户数据条目中,例如,'-123:Pass'。我已经将数据归结到了显示效果的最小值。 – RFaneuf

回答

0

当SQL服务器解析ORDER BY,它不仅从整理中决定行的顺序,而且,正如你已经发现的那样,从数据类型。

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

因此,由于ORDER BY是最后,比较二进制,你的负值首先为nvarchar。

如果您对数据的实际存储方式更感兴趣,那么可能对您感兴趣的书“Microsoft SQL Server Internals”。有一整节讨论这个问题。

编辑:

要看到,数据的实际存储里面分贝,看到这个片段:

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减号首先出现。

+0

我不确定我了解您的回复;在数据类型排序中发生'-'的地方,不应该以'-'开头的所有条目一起排序吗? – RFaneuf

+0

是的,这是正确的。我觉得奇怪的是'''条目与其他条目交错。如果nvarchar以不同的方式处理'-',例如按照ASCII字母数字顺序排列,我仍然期望所有以'-'开始的条目排序在一起。 – RFaneuf

+0

我再次检查 - 如果您使用_BIN整理,二进制整理,请参阅https://msdn.microsoft.com/en-us/library/ms143515(v=sql.105).aspx二进制整理是使用不同的字符排序方法,特别是nvarchars。使用_BIN2整理应该有所帮助。 – sqlady