看似相同的 WHERE 子句返回不同的结果

Phi*_*ord 12 sql-server t-sql sql-server-2016

我有一个名为 的表[User]。下面是一个从表中查找一个用户的简单查询:

\n
SELECT  [Username]\n      ,[FirstName]\n      ,[LastName]\n      ,[EmailAddress]\n      ,[IsStaff]\n      ,[ExternalID]\n      ,[LastLogin]\nFROM [dbo].[User]\nWHERE [Username] = 'pskalhaq' -- 1\n--WHERE [Username] = '\xe2\x80\x8fpskalhaq' -- 2\n
Run Code Online (Sandbox Code Playgroud)\n

当我运行此查询时,我得到一条记录,这正是我所期望的。当我注释掉WHERE标记为“1”的子句并取消注释标记为“2”的子句时,我没有得到任何结果。这很奇怪,因为引号之间的值是相同的。

\n

我发现这个问题是因为我编写的一个 Web 应用程序使用实体框架从另一个表获取记录,[Appointment]由于表中没有相关记录而抛出错误[User],即使该[Appointment]表包含不可为空的记录外键连接到[User]表。这似乎是不可能的。我在 SSMS 中运行了等效查询(2892 是给我带来麻烦的约会记录的 ID):

\n
SELECT a.[ID] AS [AppointmentID]\n    ,a.[StudentUsername] -- This is the foreign key in the Appointment table\n    ,u.[Username] -- This is the primary key in the User table\nFROM [dbo].[Appointment] a\nJOIN [dbo].[User] u \nON u.[Username] = a.[StudentUsername]\nWHERE a.[ID] = 2892\n
Run Code Online (Sandbox Code Playgroud)\n

这返回了一个结果,正如预期的那样。所以[Appointment]记录确实有一个链接[User]记录。此时,我认为这一定是实体框架中的一个错误(剧透:似乎不是,这就是我在这里发帖而不是在 Stack Overflow 上发帖的原因!)。

\n

但奇怪的是,如果我从查询结果中的[StudentUsername]或列中复制值并将该值粘贴到上面第一个查询的子句中,我不会从[Username]WHERE[User]表中得到任何结果。如果我在查询中手动输入用户名,情况也是如此。

\n

此时我以为我疯了,所以我在上面进行了查询[User]表上运行了一个查询,以返回按用户名排序的所有记录:

\n
SELECT [Username]\nFROM [dbo].[User]\nORDER BY [Username]\n
Run Code Online (Sandbox Code Playgroud)\n

我向下滚动到我看似失踪的用户应该在的位置,你瞧,记录就在那里!所以我从结果[Username]中的列中复制了值,将其粘贴到我的第一个查询中,突然该查询按预期返回了单个用户结果。

\n

这里发生了什么?这是 SQL Server 中的某种错误吗?我说 SQL Server 而不是 SSMS,因为似乎很可能同样的问题导致实体框架也说约会没有关联的用户。除非我碰巧复制了值,否则同一个查询怎么可能不返回结果WHERE除非我碰巧从同一个表上另一个查询的结果中

\n

PS 如果相关的话, 和[Appointment].[StudentUsername][User].[Username]都是NVARCHAR(200)。该数据库在 SQL Server 2016 SP2 上运行。

\n

Dav*_*oft 23

它们是不同的字符串。

\n
select cast(N\'pskalhaq\' as varbinary(200)) \nunion all\nselect cast(N\'\xe2\x80\x8fpskalhaq\' as varbinary(200)) \n
Run Code Online (Sandbox Code Playgroud)\n

输出

\n
    0x700073006B0061006C00680061007100\n0x0F20700073006B0061006C00680061007100\n
Run Code Online (Sandbox Code Playgroud)\n

第二个将 U+200F* unicode RIGHT-TO-LEFT MARK 作为第一个字符,因此它在二进制排序规则中不会比较为相等,也许在其他排序规则中也是如此。

\n

并且在转换为 时,字符串也不比较相等varchar,如在此查询中,其中字符串是非 unicode 文字。因此,查询文本中的 unicode 字符将被转换为数据库排序规则的代码页作为varchar.

\n
select \'\xe2\x80\x8fpskalhaq\' \n
Run Code Online (Sandbox Code Playgroud)\n

输出

\n
?pskalhaq\n
Run Code Online (Sandbox Code Playgroud)\n

U+200F 映射到 0x3F,即普通的“?” 在单字节拉丁代码页中。然后,在与nvarchar列进行比较之前,必须根据数据类型优先级规则varchar将值转换为nvarchar值。

\n

当转换为nvarchar“?”时 保留为“?”,字符串变为 N\'?pskalhaq\',与 N\'pskalhaq\' 或 N\'\xe2\x80\x8fpskalhaq\' 都不匹配。

\n

*0x0F20 = U+200F,因为字符以小端字节顺序存储。

\n