SQL 服务器:ISNULL(复合 NULL 条件,'a string')returns 只有第一个字符,在某些情况下
SQL Server : ISNULL(compound NULL condition, 'a string') returns only the 1st character, under certain circumstance(s)
我是一个自学成才的 SQL 用户。对于我正在编写的视图,我正在尝试开发一个 'conditional LEFT
' 字符串拆分命令(大概稍后会加入 'conditional RIGHT
' - 由此:
- 如果一个字符串(我们称它为 'haystack')包含一个特定的模式(我们称它为 'needle'),它将被修剪到该模式的左侧
- 否则,整个字符串将原样传递。
所以,如果我们的模式是“-”,
- 'A long string - containing the pattern' 将输出为 'A long string'
- 'A string without the pattern' 将按原样返回。
我没有使用最粗暴的方法来做到这一点,而是试图想出一种避免重复任何子句的方法(例如 if 0 < CHARINDEX
, then take CHARINDEX
- 1 等),而是利用条件 NULL
ing.
然而 - 这就是我尝试发挥创造力的结果 - 我遇到了一个看似非常基本的绊脚石。请观察以下代码和结果,并让我知道您是否可以复制它 - 以及它是否是错误或我错过了一些特殊的东西。我已经在 SQL Server 2008 R2 和 2014 Express 版本上对此进行了测试。
select
-- ISNULL: returns 'a big old string'
ISNULL(null, 'a big old string'),
-- NULLIF: returns NULL
left(
'a big old string',
nullif
(
CHARINDEX
(
'needle',
'haystack'
), 0
) - 1
),
-- combined: returns just 'a' (1st character of ISNULL condition)
ISNULL(
left
(
'a big old string', -- the input string. In reality, this would be a column alias, etc.
nullif
(
CHARINDEX -- Search for the splitting pattern
(
'needle',
'haystack'
), 0 -- If it's not found, return NULL instead of the usual 0
) - 1 -- so that this subtraction produces a NULL, not an invalid negative index
),
'a big old string' -- If the pattern was not found, we should return the input unaltered
);
/*
---------------- ---- ----
a big old string NULL a
(1 row(s) affected)
*/
为什么这 2 个子句单独运行时如预期的那样工作,但是 当我将它们组合在一起时,我没有得到它们效果的总和,我只得到 的第一个字符 ISNULL
字符串 - 'a'?
是否有某种隐含的 CAST
到 varchar(1)
?故意 cast
到 varchar(max)
没有任何区别。这里还能发生什么?
我是不是在做傻事?因为从这里开始,我无法弄清楚我做错了什么,所以它看起来真的像是一个错误。我希望 2014 年的测试能够证明它是旧版 2008 R2 中的一个错误,但遗憾的是,它们的行为相同(或者更确切地说,不相同)。
在此先感谢您,希望能将我从可能是一个困惑的生存危机之夜中拯救出来。
这是 isnull
和 coalesce
之间的区别——因为你的第一个参数 isnull 是 char(1),这将是 return 值的类型陈述。使用 coalesce 你会得到正确的结果。
Returns 与 check_expression 类型相同。如果作为 check_expression 提供文字 NULL,returns 是 replacement_value 的数据类型。如果作为 check_expression 提供文字 NULL 并且未提供 replacement_value,则 return 是一个整数。
Returns 具有最高数据类型优先级的表达式的数据类型。如果所有表达式都不可为空,则结果类型为不可为空。
在我看来你把一件简单的事情复杂化了。
此 sql 代码应按照您的描述执行:
Declare @SomeString varchar(max) = 'asdf asdf - cvbncvbn',
@Needle varchar(100) = '-'
DECLARE @NeedlePattern varchar(102) = '%' + @Needle + '%'
SELECT CASE WHEN PATINDEX(@NeedlePattern, @SomeString) > 0 THEN
LEFT(@SomeString, PATINDEX(@NeedlePattern, @SomeString) - LEN(@NeedlePattern)+1)
ELSE
@SomeString
END
这个问题有两个部分,第一个是ISNULL
运算符的性质,它将使用第一个参数的数据类型和长度。一个简单的例子是:
DECLARE @A CHAR(1) = NULL,
@B VARCHAR(MAX) = 'This is a test';
SELECT TOP 1 Test = ISNULL(@A, @B);
这个returnsT
并检查执行计划XML我们可以看到"This is a Test"
到CHAR(1)
的隐式转换:
<ScalarOperator ScalarString="isnull([@A],CONVERT_IMPLICIT(char(1),[@B],0))">
<Intrinsic FunctionName="isnull">
<ScalarOperator>
<Identifier>
<ColumnReference Column="@A" />
</Identifier>
</ScalarOperator>
<ScalarOperator>
<Convert DataType="char" Length="1" Style="0" Implicit="true">
<ScalarOperator>
<Identifier>
<ColumnReference Column="@B" />
</Identifier>
</ScalarOperator>
</Convert>
</ScalarOperator>
</Intrinsic>
</ScalarOperator>
你的例子并不是那么简单,因为你没有像上面那样很好地定义你的类型,但是如果我们定义了数据类型:
DECLARE @A VARCHAR(MAX) = 'a big old string',
@B VARCHAR(MAX) = 'needle',
@C VARCHAR(MAX) = 'haystack';
SELECT TOP 1 ISNULL(LEFT(@A, NULLIF(CHARINDEX(@B, @C), 0) - 1), @A);
我们得到了预期的结果。所以在幕后发生了其他事情。查询计划没有深入研究不断评估的内部工作原理,但下面演示了正在发生的事情:
SELECT Test = LEFT('a big old string', NULLIF(CHARINDEX('needle', 'haystack'), 0) - 1)
INTO #T;
SELECT t.name, c.max_length
FROM tempdb.sys.columns AS c
INNER JOIN sys.types AS t
ON t.system_type_id = c.system_type_id
AND t.user_type_id = c.user_type_id
WHERE [object_id] = OBJECT_ID(N'tempdb..#T');
----------------
name max_length
varchar 1
基本上,通过将 SELECT INTO
语法与您的左侧表达式一起使用,表明当 NULL 长度传递给 LEFT
时,结果数据类型为 VARCHAR(1)
、然而,情况并非总是如此。如果我只是将 NULL
硬编码到 LEFT
函数中:
SELECT Test = LEFT('a big old string', NULL)
INTO #T;
--------------------
name max_length
varchar 16
然后你得到传递的字符串的长度,但是一个 case 表达式应该被优化为相同的东西,再次产生长度 1:
SELECT TOP 1 Test = LEFT('a big old string', CASE WHEN 1 = 1 THEN NULL ELSE 1 END)
INTO #T;
----------------
name max_length
varchar 1
我怀疑它与 VARCHAR
的默认行为有关,其中默认长度为 1,例如:
DECLARE @A VARCHAR = 'This is a Test';
SELECT Value = @A, -- T
MaxLength = SQL_VARIANT_PROPERTY(@A, 'MaxLength') -- 1
但我无法告诉您为什么您会看到 NULL
和 CASE WHEN 1 = 1 THEN NULL ELSE 1 END
的不同行为。如果您想了解不断评估中发生的事情的真相,我认为您可能需要在 DBA 站点上重新询问,并希望真正的 SQL 服务器专家之一能够接受它。
总而言之,LEFT(<constant>, <constant expression>)
where <constant expression>
yields NULL
被隐式类型化为 VARCHAR(1)
,这个隐式类型用于 ISNULL
求值。
就其价值而言,如果您明确键入 LEFT
函数的结果,那么您将获得预期的结果:
SELECT ISNULL(
CAST(
LEFT(
'a big old string',
NULLIF(CHARINDEX('needle', 'haystack'), 0) - 1
)
AS VARCHAR(MAX))
, 'a big old string');
另外一点是,当你说你不想重复任何表达式时(如果 0 < CHARINDEX,则取 CHARINDEX - 1,等等),有两件事你应该知道,第一是NULLIF(<expression>, <value>)
扩展为case表达式-CASE WHEN <expression> = <value> THEN NULL ELSE <expression> END
,所以重复,第二个是这个不要紧,SQL服务器可以识别这是同一个表达式使用了两次,并且会计算一次,每次使用都引用相同的结果。
我是一个自学成才的 SQL 用户。对于我正在编写的视图,我正在尝试开发一个 'conditional LEFT
' 字符串拆分命令(大概稍后会加入 'conditional RIGHT
' - 由此:
- 如果一个字符串(我们称它为 'haystack')包含一个特定的模式(我们称它为 'needle'),它将被修剪到该模式的左侧
- 否则,整个字符串将原样传递。
所以,如果我们的模式是“-”,
- 'A long string - containing the pattern' 将输出为 'A long string'
- 'A string without the pattern' 将按原样返回。
我没有使用最粗暴的方法来做到这一点,而是试图想出一种避免重复任何子句的方法(例如 if 0 < CHARINDEX
, then take CHARINDEX
- 1 等),而是利用条件 NULL
ing.
然而 - 这就是我尝试发挥创造力的结果 - 我遇到了一个看似非常基本的绊脚石。请观察以下代码和结果,并让我知道您是否可以复制它 - 以及它是否是错误或我错过了一些特殊的东西。我已经在 SQL Server 2008 R2 和 2014 Express 版本上对此进行了测试。
select
-- ISNULL: returns 'a big old string'
ISNULL(null, 'a big old string'),
-- NULLIF: returns NULL
left(
'a big old string',
nullif
(
CHARINDEX
(
'needle',
'haystack'
), 0
) - 1
),
-- combined: returns just 'a' (1st character of ISNULL condition)
ISNULL(
left
(
'a big old string', -- the input string. In reality, this would be a column alias, etc.
nullif
(
CHARINDEX -- Search for the splitting pattern
(
'needle',
'haystack'
), 0 -- If it's not found, return NULL instead of the usual 0
) - 1 -- so that this subtraction produces a NULL, not an invalid negative index
),
'a big old string' -- If the pattern was not found, we should return the input unaltered
);
/*
---------------- ---- ----
a big old string NULL a
(1 row(s) affected)
*/
为什么这 2 个子句单独运行时如预期的那样工作,但是 当我将它们组合在一起时,我没有得到它们效果的总和,我只得到 的第一个字符 ISNULL
字符串 - 'a'?
是否有某种隐含的 CAST
到 varchar(1)
?故意 cast
到 varchar(max)
没有任何区别。这里还能发生什么?
我是不是在做傻事?因为从这里开始,我无法弄清楚我做错了什么,所以它看起来真的像是一个错误。我希望 2014 年的测试能够证明它是旧版 2008 R2 中的一个错误,但遗憾的是,它们的行为相同(或者更确切地说,不相同)。
在此先感谢您,希望能将我从可能是一个困惑的生存危机之夜中拯救出来。
这是 isnull
和 coalesce
之间的区别——因为你的第一个参数 isnull 是 char(1),这将是 return 值的类型陈述。使用 coalesce 你会得到正确的结果。
Returns 与 check_expression 类型相同。如果作为 check_expression 提供文字 NULL,returns 是 replacement_value 的数据类型。如果作为 check_expression 提供文字 NULL 并且未提供 replacement_value,则 return 是一个整数。
Returns 具有最高数据类型优先级的表达式的数据类型。如果所有表达式都不可为空,则结果类型为不可为空。
在我看来你把一件简单的事情复杂化了。
此 sql 代码应按照您的描述执行:
Declare @SomeString varchar(max) = 'asdf asdf - cvbncvbn',
@Needle varchar(100) = '-'
DECLARE @NeedlePattern varchar(102) = '%' + @Needle + '%'
SELECT CASE WHEN PATINDEX(@NeedlePattern, @SomeString) > 0 THEN
LEFT(@SomeString, PATINDEX(@NeedlePattern, @SomeString) - LEN(@NeedlePattern)+1)
ELSE
@SomeString
END
这个问题有两个部分,第一个是ISNULL
运算符的性质,它将使用第一个参数的数据类型和长度。一个简单的例子是:
DECLARE @A CHAR(1) = NULL,
@B VARCHAR(MAX) = 'This is a test';
SELECT TOP 1 Test = ISNULL(@A, @B);
这个returnsT
并检查执行计划XML我们可以看到"This is a Test"
到CHAR(1)
的隐式转换:
<ScalarOperator ScalarString="isnull([@A],CONVERT_IMPLICIT(char(1),[@B],0))">
<Intrinsic FunctionName="isnull">
<ScalarOperator>
<Identifier>
<ColumnReference Column="@A" />
</Identifier>
</ScalarOperator>
<ScalarOperator>
<Convert DataType="char" Length="1" Style="0" Implicit="true">
<ScalarOperator>
<Identifier>
<ColumnReference Column="@B" />
</Identifier>
</ScalarOperator>
</Convert>
</ScalarOperator>
</Intrinsic>
</ScalarOperator>
你的例子并不是那么简单,因为你没有像上面那样很好地定义你的类型,但是如果我们定义了数据类型:
DECLARE @A VARCHAR(MAX) = 'a big old string',
@B VARCHAR(MAX) = 'needle',
@C VARCHAR(MAX) = 'haystack';
SELECT TOP 1 ISNULL(LEFT(@A, NULLIF(CHARINDEX(@B, @C), 0) - 1), @A);
我们得到了预期的结果。所以在幕后发生了其他事情。查询计划没有深入研究不断评估的内部工作原理,但下面演示了正在发生的事情:
SELECT Test = LEFT('a big old string', NULLIF(CHARINDEX('needle', 'haystack'), 0) - 1)
INTO #T;
SELECT t.name, c.max_length
FROM tempdb.sys.columns AS c
INNER JOIN sys.types AS t
ON t.system_type_id = c.system_type_id
AND t.user_type_id = c.user_type_id
WHERE [object_id] = OBJECT_ID(N'tempdb..#T');
----------------
name max_length
varchar 1
基本上,通过将 SELECT INTO
语法与您的左侧表达式一起使用,表明当 NULL 长度传递给 LEFT
时,结果数据类型为 VARCHAR(1)
、然而,情况并非总是如此。如果我只是将 NULL
硬编码到 LEFT
函数中:
SELECT Test = LEFT('a big old string', NULL)
INTO #T;
--------------------
name max_length
varchar 16
然后你得到传递的字符串的长度,但是一个 case 表达式应该被优化为相同的东西,再次产生长度 1:
SELECT TOP 1 Test = LEFT('a big old string', CASE WHEN 1 = 1 THEN NULL ELSE 1 END)
INTO #T;
----------------
name max_length
varchar 1
我怀疑它与 VARCHAR
的默认行为有关,其中默认长度为 1,例如:
DECLARE @A VARCHAR = 'This is a Test';
SELECT Value = @A, -- T
MaxLength = SQL_VARIANT_PROPERTY(@A, 'MaxLength') -- 1
但我无法告诉您为什么您会看到 NULL
和 CASE WHEN 1 = 1 THEN NULL ELSE 1 END
的不同行为。如果您想了解不断评估中发生的事情的真相,我认为您可能需要在 DBA 站点上重新询问,并希望真正的 SQL 服务器专家之一能够接受它。
总而言之,LEFT(<constant>, <constant expression>)
where <constant expression>
yields NULL
被隐式类型化为 VARCHAR(1)
,这个隐式类型用于 ISNULL
求值。
就其价值而言,如果您明确键入 LEFT
函数的结果,那么您将获得预期的结果:
SELECT ISNULL(
CAST(
LEFT(
'a big old string',
NULLIF(CHARINDEX('needle', 'haystack'), 0) - 1
)
AS VARCHAR(MAX))
, 'a big old string');
另外一点是,当你说你不想重复任何表达式时(如果 0 < CHARINDEX,则取 CHARINDEX - 1,等等),有两件事你应该知道,第一是NULLIF(<expression>, <value>)
扩展为case表达式-CASE WHEN <expression> = <value> THEN NULL ELSE <expression> END
,所以重复,第二个是这个不要紧,SQL服务器可以识别这是同一个表达式使用了两次,并且会计算一次,每次使用都引用相同的结果。