1

我希望这对 SQL 专家来说是一个有趣的谜题。

当我运行以下查询时,我希望它不会返回任何结果。

-- Create a table variable Note: This same behaviour occurs in standard tables.

DECLARE @TestResults TABLE (Id int IDENTITY(1,1) NOT NULL, Foo VARCHAR(100) NOT NULL, About VARCHAR(1000) NOT NULL)

-- Add some test data Note: Without space, space prefix and space suffix

INSERT INTO @TestResults(Foo, About) VALUES('Bar', 'No spaces')
INSERT INTO @TestResults(Foo, About) VALUES('Bar ', 'Space Suffix')
INSERT INTO @TestResults(Foo, About) VALUES(' Bar', 'Space prefix')

-- SELECT statement that is filtered by a value without a space and also a value with a space suffix

SELECT 
     t.Foo
     , t.About
FROM @TestResults t
WHERE t.Foo like 'Bar '
AND t.Foo like 'Bar'
AND t.Foo = 'Bar '
AND t.Foo = 'Bar'

结果返回一行:

[Foo]  [About]
Bar    Space Suffix

问题是人们正在从电子邮件等复制和粘贴值,并且他们以某种方式进入表格。我正在将此作为一个单独的问题进行研究,因为我将 LTRIM(RTRIM(Foo)) 作为 INSERT 和 UPDATE 触发器,但有些人正在以某种方式通过网络。

我需要更多地了解这种行为以及我应该如何解决它。

还值得注意的是 LEN(Foo) 也很奇怪,如下所示:

DECLARE @TestResults TABLE (Id int IDENTITY(1,1) NOT NULL, Foo VARCHAR(100) NOT NULL, About VARCHAR(1000) NOT NULL)
INSERT INTO @TestResults(Foo, About) VALUES('Bar', 'No spaces')
INSERT INTO @TestResults(Foo, About) VALUES('Bar ', 'Space Suffix')
INSERT INTO @TestResults(Foo, About) VALUES(' Bar', 'Space prefix')

SELECT 
     t.Foo
     , LEN(Foo) [Length]
     , t.About
FROM @TestResults t

给出以下结果:

[Foo]   [Length]  [About]
Bar     3         No spaces
Bar     3         Space Suffix
 Bar    4         Space prefix

没有任何横向思考,我需要将 WHERE 子句更改为什么才能按预期返回 0 结果?

4

3 回答 3

1

答案是添加以下子句:

AND DATALENGTH(t.Foo) = DATALENGTH('Bar')

运行以下查询...

DECLARE @Chars TABLE (CharNumber INT NOT NULL)

DECLARE @CharNumber INT = 0

WHILE(@CharNumber <= 255)
    BEGIN
        INSERT INTO @Chars(CharNumber) VALUES(@CharNumber)

        SET @CharNumber = @CharNumber + 1

    END

SELECT 
    CharNumber
    , IIF('Test' = 'Test' + CHAR(CharNumber),1,0) ['Test' = 'Test' + CHAR(CharNumber)]
    , IIF('Test' LIKE 'Test' + CHAR(CharNumber),1,0) ['Test' LIKE 'Test' + CHAR(CharNumber)]
    , IIF(LEN('Test') = LEN('Test' + CHAR(CharNumber)),1,0) [LEN('Test') = LEN('Test' + CHAR(CharNumber))]
    , IIF(DATALENGTH('Test') = DATALENGTH('Test' + CHAR(CharNumber)),1,0) [DATALENGTH('Test') = DATALENGTH('Test' + CHAR(CharNumber))]
FROM @Chars
WHERE ('Test' = 'Test' + CHAR(CharNumber))
OR ('Test' LIKE 'Test' + CHAR(CharNumber))
OR (LEN('Test') = LEN('Test' + CHAR(CharNumber)))
ORDER BY CharNumber

...产生以下结果...

CharNumber  'Test' = 'Test' + CHAR(CharNumber)  'Test' LIKE 'Test' + CHAR(CharNumber)   LEN('Test') = LEN('Test' + CHAR(CharNumber))    DATALENGTH('Test') = DATALENGTH('Test' + CHAR(CharNumber))
0           1                                   1                                       0                                               0
32          1                                   0                                       1                                               0
37          0                                   1                                       0                                               0

DATALENGTH 可用于测试两个 VARCHAR 是否相等,因此可以将原始查询更正如下:

-- Create a table variable Note: This same behaviour occurs in standard tables.

DECLARE @TestResults TABLE (Id int IDENTITY(1,1) NOT NULL, Foo VARCHAR(100) NOT NULL, About VARCHAR(1000) NOT NULL)

-- Add some test data Note: Without space, space prefix and space suffix

INSERT INTO @TestResults(Foo, About) VALUES('Bar', 'No spaces')
INSERT INTO @TestResults(Foo, About) VALUES('Bar ', 'Space Suffix')
INSERT INTO @TestResults(Foo, About) VALUES(' Bar', 'Space prefix')

-- SELECT statement that is filtered by a value without a space and also a value with a space suffix

SELECT 
     t.Foo
     , t.About
FROM @TestResults t
WHERE t.Foo like 'Bar '
AND t.Foo like 'Bar'
AND t.Foo = 'Bar ' 
AND t.Foo = 'Bar' 
AND DATALENGTH(t.Foo) = DATALENGTH('Bar') -- Additional clause

我还做了一个函数来代替 =

ALTER FUNCTION dbo.fVEQ( @VarCharA VARCHAR(MAX), @VarCharB VARCHAR(MAX) ) 
RETURNS BIT 
WITH SCHEMABINDING
AS
BEGIN
    -- Added by WonderWorker on 18th March 2020

    DECLARE @Result BIT = IIF(
        (@VarCharA = @VarCharB AND DATALENGTH(@VarCharA) = DATALENGTH(@VarCharB))

    , 1, 0)

    RETURN @Result

END

..这是对用作尾随字符的所有 256 个字符的测试,以证明它有效..

-- Test fVEQ with all 256 characters

DECLARE @Chars TABLE (CharNumber INT NOT NULL)

DECLARE @CharNumber INT = 0

WHILE(@CharNumber <= 255)
    BEGIN
        INSERT INTO @Chars(CharNumber) VALUES(@CharNumber)

        SET @CharNumber = @CharNumber + 1

    END

SELECT 
    CharNumber
    , dbo.fVEQ('Bar','Bar' + CHAR(CharNumber)) [fVEQ Trailing Char Test]
    , dbo.fVEQ('Bar','Bar') [fVEQ Same test]
    , dbo.fVEQ('Bar',CHAR(CharNumber) + 'Bar') [fVEQ Leading Char Test]
FROM @Chars
WHERE (dbo.fVEQ('Bar','Bar' + CHAR(CharNumber)) = 1)
AND (dbo.fVEQ('Bar','Bar') = 0)
AND (dbo.fVEQ('Bar',CHAR(CharNumber) + 'Bar') = 1)
于 2020-03-18T16:20:48.343 回答
1

在字符串比较中忽略尾随空格的原因是因为固定长度字符串字段的概念,其中任何短于固定长度的内容都会自动用空格右填充。这种固定长度的字段无法区分有意义的尾随空格和填充。

为何存在固定长度字符串字段的理由是,它们在许多情况下显着提高了性能,并且在设计 SQL 时,它对于基于字符的终端很常见(通常将尾随空格处理等同于填充),使用等宽打印的报告字体(使用尾随空格进行填充和对齐),以及数据存储和交换格式(使用固定长度字段代替广泛且昂贵的分隔符和复杂的解析逻辑),都围绕固定长度字段,所以有在处理的所有阶段都与此概念紧密结合。

当比较两个具有相同固定长度的固定长度字段时,文字比较当然是可能的,并且会产生正确的结果。

但是,当比较给定固定长度的固定长度字段与不同固定长度的固定长度字段时,所需的行为永远不会是在比较中包含尾随空格,因为两个这样的字段永远不会简单地匹配由于它们不同的固定长度。较短的字段可以被强制转换并填充到较长的长度(至少在概念上,如果不是物理上的话),但尾随空间仍将被视为填充而不是有意义的。

在将固定长度字段与可变长度字段进行比较时,所需的行为也可能永远不会在比较中包含尾随空格。尝试将含义归因于比较的可变长度侧的尾随空格的更复杂的方法只会以较慢的比较逻辑和额外的概念复杂性和潜在的错误为代价。

至于为什么可变长度到可变长度的比较忽略尾随空格,因为这里的空格原则上是有意义的,其基本原理可能是在涉及固定长度字段时保持比较行为的一致性,并且避免最常见的一种错误,因为尾随空格在数据库中的虚假程度远远超过它们的意义。

如今,从头开始设计的数据库系统可能会放弃固定长度的字段,并可能按字面意思执行所有比较,让开发人员明确处理虚假的尾随空格,但根据我的经验,这将导致额外的开发工作和远比 SQL 中的当前安排更频繁的错误,其中涉及静默忽略尾随空格的程序逻辑错误通常仅在设计用于处理非规范化数据(这是 SQL 是一种数据)的复杂字符串分解逻辑时发生特别没有针对处理进行优化)。

所以需要明确的是,这不是一个未记录的功能,而是一个设计存在的突出功能。

于 2021-02-03T00:21:18.727 回答
0

如果您将查询更改为

SELECT 
     Foo
     , About
     , CASE WHEN Foo LIKE 'Bar ' THEN 'T' ELSE 'F' END As Like_Bar_Space
     , CASE WHEN Foo LIKE 'Bar'  THEN 'T' ELSE 'F' END As Like_Bar
     , CASE WHEN Foo =    'Bar ' THEN 'T' ELSE 'F' END As EQ_Bar_Space
     , CASE WHEN Foo =    'Bar'  THEN 'T' ELSE 'F' END As EQ_Bar
FROM @TestResults

它为您提供了更好的概览,因为您可以分别查看不同条件的结果:

Foo     About         Like_Bar_Space   Like_Bar   EQ_Bar_Space   EQ_Bar
------  ------------  ---------------  ---------  -------------  ------
Bar     No spaces      F                T          T              T
Bar     Space Suffix   T                T          T              T
 Bar    Space prefix   F                F          F              F

看起来等于=忽略了搜索字符串和模式中的尾随空格。然而,LIKE 不会忽略模式中的尾随空格,而是忽略搜索字符串中的额外尾随空格。前导空格永远不会被忽略。

我不知道那里的错误条目是如何出现的,但是您可以使用以下方法修复它们

UPDATE @TestResults SET Foo = TRIM(Foo)

您可以使用以下方法进行尾随空间敏感测试:

WHERE t.Foo + ";" = pattern + ";" 

您可以使用以下方法进行尾随空格不敏感测试:

WHERE RTRIM(t.Foo) = RTRIM(pattern)
于 2020-03-17T15:56:00.533 回答