Sql Server:比较where子句中的varchar类型值不返回结果

Posted

技术标签:

【中文标题】Sql Server:比较where子句中的varchar类型值不返回结果【英文标题】:Sql Server: Compare varchar type value in where clause doesn't return results 【发布时间】:2013-11-25 09:14:37 【问题描述】:

我在 AWS RDS 数据库服务中有一个 SQL Server 2012 网络版。排序规则是默认的:SQL_Latin1_General_CP1_CI_AS

在其中一个表中,我有一列 MODIFIED_BY,类型为 varchar(128) - 列排序规则是默认值。

存储在此列中的常用值是 GUIDS,除了一些硬编码为 System 的值。如您所知,它是一个用于存储审计跟踪事务的表,我们在其中存储 id 或 System(如果在迁移过程中进行了修改)。

无论如何-以上所有内容都只是上下文。现在奇怪的问题:

如果我运行以下命令,我不会得到任何记录,尽管我应该有:

select *
from AUDIT_LOG_TRANSACTIONS alt
where lower(ltrim(rtrim(alt.MODIFIED_BY)))='system'

select *
from AUDIT_LOG_TRANSACTIONS alt
where lower(ltrim(rtrim(alt.MODIFIED_BY)))=convert(varchar,'system')

select *
from AUDIT_LOG_TRANSACTIONS alt
where lower(ltrim(rtrim(alt.MODIFIED_BY)))=convert(varchar(128),'system')

如果我运行以下命令,我会得到记录:

select *
from AUDIT_LOG_TRANSACTIONS alt
where alt.MODIFIED_BY like '%System%'

select *
from AUDIT_LOG_TRANSACTIONS alt
where lower(ltrim(rtrim(CAST(alt.MODIFIED_BY AS nvarchar(max)))))='system'

更新 这是表创建脚本:

SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

SET ANSI_PADDING ON
GO

CREATE TABLE [dbo].[AUDIT_LOG_TRANSACTIONS](
    [AUDIT_LOG_TRANSACTION_ID] [int] IDENTITY(1,1) NOT NULL,
    [DATABASE] [nvarchar](128) NOT NULL,
    [TABLE_NAME] [nvarchar](261) NOT NULL,
    [TABLE_SCHEMA] [nvarchar](261) NOT NULL,
    [AUDIT_ACTION_ID] [tinyint] NOT NULL,
    [HOST_NAME] [varchar](128) NOT NULL,
    [APP_NAME] [varchar](128) NOT NULL,
    [MODIFIED_BY] [varchar](128) NOT NULL,
    [MODIFIED_DATE] [datetime] NOT NULL,
    [AFFECTED_ROWS] [int] NOT NULL,
    [SYSOBJ_ID]  AS (object_id([TABLE_NAME])),
PRIMARY KEY CLUSTERED 
(
    [AUDIT_LOG_TRANSACTION_ID] 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

SET ANSI_PADDING OFF
GO

ALTER TABLE [dbo].[AUDIT_LOG_TRANSACTIONS] ADD  DEFAULT (db_name()) FOR [DATABASE]
GO

ALTER TABLE [dbo].[AUDIT_LOG_TRANSACTIONS] ADD  CONSTRAINT [DF_AUDIT_LOG_TRANSACTIONS_MODIFIED_BY]  DEFAULT ('System') FOR [MODIFIED_BY]
GO

【问题讨论】:

听起来你可能有一些字符值是字母可见字符。您可以添加 LEN(MODIFIED_BY) 并轻松确定 id 是这种情况。 我对上面的哪个查询返回结果感到困惑。你能把一起工作的和不一起工作的分组吗?您说排序规则是 SQL_Latin1_General_CP1_CI_AS - 但这是服务器排序规则、数据库排序规则还是 system 列的排序规则?你能确认这三个都是一样的吗?您可以为表格生成一个脚本并将其发布到问题中吗? @AaronBertrand - 我已经更新了创建脚本的问题并对 sql 查询结果进行了分组。 【参考方案1】:

你的字符串中可能有一些“难以看到”的字符吗? 为了消除这种可能性,我会运行这样的查询:

select alt.*, LEN(alt.MODIFIED_BY) as 'Length', cast(alt.MODIFIED_BY as varbinary(max)) as 'Bytes' from AUDIT_LOG_TRANSACTIONS alt where alt.MODIFIED_BY like '%System%' 

检查 Length 和 Bytes 列是否符合您的预期。

【讨论】:

看起来返回的长度是 64,但应该是 6。varbinary 在值 System 编码之后返回尾随零。有点像这样:0x53797374656D0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 @user2417063 - 因为那是您必须插入的内容。我看到这是一个审计表。 MODIFIED_BY 是否通过将CONTEXT_INFO 中的信息传递给触发器来填充? @MartinSmith - 是的,它来自 CONTEXT_INFO。所以我想我必须将用于 CONTEXT_INFO 的 varbinary 变量的长度设置为字符串的实际长度,对吧? @el_manolos - CONTEXT_INFO 将始终用尾随空字节填充到长度 128。为什么数据的长度是 64?你只看前半部分吗?可能最简单的方法是自己填充尾随空格。 Declare @UserName BINARY(64) = CAST(CAST('System' AS CHAR(64)) AS BINARY(64)); SET CONTEXT_INFO @UserName; SELECT RTRIM(SUBSTRING(CONTEXT_INFO(),1,64)) @MartinSmith - 这就是我在代码中声明它的方式:“DECLARE \@BinVar varbinary(128) SET \@BinVar = CAST(N'0' AS varbinary(128)) SET CONTEXT_INFO \@BinVar".F(this.GetUserId()) 但我假设返回的长度是 64,因为它可能使用两个字节来存储字符,只是一个想法,不确定。

以上是关于Sql Server:比较where子句中的varchar类型值不返回结果的主要内容,如果未能解决你的问题,请参考以下文章

SQL Server 中的条件 WHERE 子句

SQL Server:IF .. ELSE 中的 Where 子句

WHERE 子句中的 SQL 查询子选择优化 (SQL Server)

where 子句中的 case 语句 - SQL Server

WHERE 子句中的 OR 会降低 sql 查询性能(sql server)

SQL Server:性能问题:WHERE 子句中的 OR 语句替换