我是一位自学成才,模糊不清的SQL用户。对于我正在编写的视图,我正在尝试开发一个'条件LEFT
'字符串拆分命令(可能后来加入'条件RIGHT
' - 其中:
所以,如果我们的模式是' - ',
我没有使用最原始的方法来做到这一点,而是试图想出一种方法来避免重复任何条款(例如if 0 < CHARINDEX
,然后取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)
*/
为什么这两个子句单独按预期工作,但当我将它们组合起来,而不是得到它们的效果之和时,我只得到ISNULL
字符串的第一个字符 - 'a'?
是否有某种隐含的CAST
到varchar(1)
?故意cast
varchar(max)
没有任何区别。还有什么可以发生在这里?
我只是在做一些非常愚蠢的事情吗?因为从这里开始,我无法弄清楚我做错了什么,所以它看起来像是一个错误。我希望2014年的测试能证明它是旧的2008 R2中的一个错误,但唉,它们的行为相同(或者说,不是)。
提前感谢,希望能够让我远离那可能是一个令人困惑的存在危机的夜晚。
答案 0 :(得分:5)
这是isnull
和coalesce
之间的区别 - 因为你的第一个isnull参数是char(1),这将是语句返回值的类型。通过合并,您将获得正确的结果。
返回与check_expression相同的类型。如果提供了一个文字NULL作为check_expression,则返回replacement_value的数据类型。如果提供了文字NULL作为check_expression且未提供replacement_value,则返回int。
返回具有最高数据类型优先级的表达式的数据类型。如果所有表达式都是不可为空的,则结果将被输入为nonnullable。
答案 1 :(得分:1)
这个问题有两个部分,第一部分是ISNULL
运算符的性质,它将使用第一个参数的数据类型和长度。一个简单的例子是:
DECLARE @A CHAR(1) = NULL,
@B VARCHAR(MAX) = 'This is a test';
SELECT TOP 1 Test = ISNULL(@A, @B);
这将返回T
并检查执行计划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
sytax显示当NULL长度传递给LEFT
时,结果数据类型为VARCHAR(1)
,然而,情况并非总是如此。如果我只是将NULL
硬编码到LEFT
函数中:
SELECT Test = LEFT('a big old string', NULL)
INTO #T;
--------------------
name max_length
varchar 16
然后你得到传递的字符串的legnth,但是一个应该优化到同一个东西的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 Server专家会选择它。
总之,LEFT(<constant>, <constant expression>)
其中<constant expression>
产生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&lt; CHARINDEX,然后选择CHARINDEX - 1等),你应该知道两件事,第一件是NULLIF(<expression>, <value>)
扩展为案例陈述 - CASE WHEN <expression> = <value> THEN NULL ELSE <expression> END
,所以重复,第二个是没关系,SQL Server可以识别这是两次使用的相同表达式,并将评估它一次,并在每次使用时引用相同的结果。
答案 2 :(得分:0)
对我来说,就像你已经使一件简单的事情复杂化了。
这个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