当 SELECT 在带有尾随空格的 VARCHAR 中 SQL 服务器时未记录的特征
UNDOCUMENTED FEATURE when SELECT in VARCHAR with trailing whitespace SQL Server
我希望这对 SQL 专家来说是一个有趣的谜题。
当我 运行 以下查询时,我希望它 return 没有结果。
-- 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'
结果return一行:
[Foo] [About]
Bar Space Suffix
我需要更多地了解这种行为以及我应该如何解决它。
另外值得注意的是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 子句更改为什么才能达到预期的 return 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
看起来等于 =
忽略了搜索字符串和模式中的尾随 space。但是,LIKE 不会忽略模式中的尾随 space,但会忽略搜索字符串中额外的尾随 space。前导 space 永远不会被忽略。
我不知道错误的条目是如何进入的,但你可以用
修复它们
UPDATE @TestResults SET Foo = TRIM(Foo)
您可以使用以下方法进行尾随 space 敏感测试:
WHERE t.Foo + ";" = pattern + ";"
您可以使用以下方法进行尾随 space 不敏感测试:
WHERE RTRIM(t.Foo) = RTRIM(pattern)
答案是添加以下子句:
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)
之所以在字符串比较中忽略尾部白色space,是因为固定长度字符串字段的概念,其中任何短于固定长度的内容都会自动右填充 space秒。这种固定长度的字段无法区分有意义的尾随 space 和填充。
为什么固定长度的字符串字段甚至存在的基本原理是,它们在许多情况下显着提高性能,并且在设计 SQL 时,它对于基于字符的终端(通常处理尾随 spaces 相当于 padding),用 monospaced 字体打印的报告(使用尾部 spaces 进行填充和对齐),以及数据存储和交换格式(使用固定长度的字段)代替广泛且昂贵的定界符和复杂的解析逻辑),所有这些都围绕固定长度的字段,因此在处理的所有阶段都与这个概念紧密集成。
当比较两个相同固定长度的固定长度字段时,文字比较当然是可能的,并且会产生正确的结果。
但是,当将给定固定长度的固定长度字段与不同固定长度的固定长度字段进行比较时,理想的行为永远不会是在比较中包含尾随 spaces ,因为两个这样的字段永远不会仅仅因为它们不同的固定长度而在字面上匹配。可以将较短的字段转换并填充到较长的字段的长度(至少在概念上,如果不是物理上的话),但尾随 space 仍然会被视为填充而不是有意义的。
将固定长度字段与可变长度字段进行比较时,所需的行为也可能永远不会在比较中包含尾随 space。更复杂的方法试图将意义归因于比较的可变长度端的尾随 space,只会以较慢的比较逻辑和额外的概念复杂性和潜在的错误为代价。
关于为什么可变长度到可变长度的比较会忽略尾随的 spaces,因为这里的 spaces 原则上是有意义的,基本原理可能是保持比较行为的一致性,因为当涉及固定长度的字段时,以及避免最常见的错误类型,因为尾随 space 在数据库中是虚假的,远比它们有意义的多。
如今,从头开始设计的数据库系统可能会放弃固定长度的字段,并可能按字面意思执行所有比较,让开发人员明确处理虚假的尾随 space,但在我看来根据经验,与 SQL 中的当前安排相比,这将导致额外的开发工作和更频繁的错误,其中涉及静默忽略尾随 space 的程序逻辑错误通常仅在设计复杂的字符串分解时发生用于非规范化数据的逻辑(这是一种 SQL 没有专门针对处理进行优化的数据)。
明确地说,这不是未记录的功能,而是设计存在的突出功能。
我希望这对 SQL 专家来说是一个有趣的谜题。
当我 运行 以下查询时,我希望它 return 没有结果。
-- 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'
结果return一行:
[Foo] [About]
Bar Space Suffix
我需要更多地了解这种行为以及我应该如何解决它。
另外值得注意的是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 子句更改为什么才能达到预期的 return 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
看起来等于 =
忽略了搜索字符串和模式中的尾随 space。但是,LIKE 不会忽略模式中的尾随 space,但会忽略搜索字符串中额外的尾随 space。前导 space 永远不会被忽略。
我不知道错误的条目是如何进入的,但你可以用
修复它们UPDATE @TestResults SET Foo = TRIM(Foo)
您可以使用以下方法进行尾随 space 敏感测试:
WHERE t.Foo + ";" = pattern + ";"
您可以使用以下方法进行尾随 space 不敏感测试:
WHERE RTRIM(t.Foo) = RTRIM(pattern)
答案是添加以下子句:
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)
之所以在字符串比较中忽略尾部白色space,是因为固定长度字符串字段的概念,其中任何短于固定长度的内容都会自动右填充 space秒。这种固定长度的字段无法区分有意义的尾随 space 和填充。
为什么固定长度的字符串字段甚至存在的基本原理是,它们在许多情况下显着提高性能,并且在设计 SQL 时,它对于基于字符的终端(通常处理尾随 spaces 相当于 padding),用 monospaced 字体打印的报告(使用尾部 spaces 进行填充和对齐),以及数据存储和交换格式(使用固定长度的字段)代替广泛且昂贵的定界符和复杂的解析逻辑),所有这些都围绕固定长度的字段,因此在处理的所有阶段都与这个概念紧密集成。
当比较两个相同固定长度的固定长度字段时,文字比较当然是可能的,并且会产生正确的结果。
但是,当将给定固定长度的固定长度字段与不同固定长度的固定长度字段进行比较时,理想的行为永远不会是在比较中包含尾随 spaces ,因为两个这样的字段永远不会仅仅因为它们不同的固定长度而在字面上匹配。可以将较短的字段转换并填充到较长的字段的长度(至少在概念上,如果不是物理上的话),但尾随 space 仍然会被视为填充而不是有意义的。
将固定长度字段与可变长度字段进行比较时,所需的行为也可能永远不会在比较中包含尾随 space。更复杂的方法试图将意义归因于比较的可变长度端的尾随 space,只会以较慢的比较逻辑和额外的概念复杂性和潜在的错误为代价。
关于为什么可变长度到可变长度的比较会忽略尾随的 spaces,因为这里的 spaces 原则上是有意义的,基本原理可能是保持比较行为的一致性,因为当涉及固定长度的字段时,以及避免最常见的错误类型,因为尾随 space 在数据库中是虚假的,远比它们有意义的多。
如今,从头开始设计的数据库系统可能会放弃固定长度的字段,并可能按字面意思执行所有比较,让开发人员明确处理虚假的尾随 space,但在我看来根据经验,与 SQL 中的当前安排相比,这将导致额外的开发工作和更频繁的错误,其中涉及静默忽略尾随 space 的程序逻辑错误通常仅在设计复杂的字符串分解时发生用于非规范化数据的逻辑(这是一种 SQL 没有专门针对处理进行优化的数据)。
明确地说,这不是未记录的功能,而是设计存在的突出功能。