【问题标题】:SQL Server : ISNULL(compound NULL condition, 'a string') returns only the 1st character, under certain circumstance(s)SQL Server : ISNULL(compound NULL condition, 'a string') 在某些情况下只返回第一个字符
【发布时间】:2015-07-13 16:37:56
【问题描述】:

我是一个自学成才的 SQL 用户。对于我正在写的一个观点,我正在尝试开发一个“有条件的LEFT”字符串分割命令(大概稍后会加入一个“有条件的RIGHT” - 借此:

  • 如果一个字符串(我们称之为 'haystack')包含一个特定的模式(我们称之为 'needle'),它将被修剪到该模式的左侧
  • 否则,整个字符串将原封不动地传递。

所以,如果我们的模式是' - ',

  • “长字符串 - 包含模式”将输出为“长字符串”
  • “没有模式的字符串”将按原样返回。

而不是使用最粗暴的方法来做到这一点,我试图想出一种避免重复任何子句的方法(例如如果0 < CHARINDEX,然后取CHARINDEX - 1 等)和而是利用条件NULLing。

然而——这就是我尝试创造的结果——我遇到了一个看似非常基本的绊脚石。请注意下面的代码和结果,让我知道你是否可以复制它 - 因此它是一个错误还是我错过了一些特殊的东西。我已经在 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 字符串的第一个字符 - '一个'?

是否有某种隐含的CASTvarchar(1)?故意casting 到varchar(max) 没有区别。这里还能发生什么?

我只是在做一些非常愚蠢的事情吗?因为从这里,我无法弄清楚我做错了什么,所以它看起来真的像一个错误。我希望 2014 年的测试能证明它是旧 2008 R2 中的一个错误,但可惜,它们的行为相同(或者说不一样)。

在此先感谢您,希望能将我从可能是一个困惑的生存危机之夜中解救出来。

【问题讨论】:

  • 不确定“为什么不将这两个子句组合在一起时为什么会起作用”这个问题的答案:SELECT SUBSTRING(@stringToSearch, 0, ISNULL(NULLIF(CHARINDEX(@searchString, @stringToSearch), 0), LEN(@stringToSearch) + 1))

标签: sql sql-server string isnull nullif


【解决方案1】:

这就是isnullcoalesce 之间的区别——因为isnull 的第一个参数是char(1),这将是语句返回值的类型。使用 coalesce 你会得到正确的结果。

Isnull:

返回与 check_expression 相同的类型。如果提供文字 NULL 作为 check_expression,则返回 replacement_value 的数据类型。如果提供了文字 NULL 作为 check_expression 并且没有提供 replacement_value,则返回一个 int。

Coalesce:

返回具有最高数据类型优先级的表达式的数据类型。如果所有表达式都不可为空,则结果类型为不可空。

【讨论】:

  • 非常感谢。 @GarethD 赢得了解释 为什么 我的“isnull 的第一个参数是 char(1)”!
【解决方案2】:

这个问题有两个部分,第一个是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 语法与您的左表达式一起使用,表明当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

但我无法告诉您为什么您会看到 NULLCASE WHEN 1 = 1 THEN NULL ELSE 1 END 的行为不同。如果您想了解持续评估中发生的情况,我认为您可能需要在 DBA 站点上重新询问,并希望真正的 SQL Server 大师之一能够接受。

总之,LEFT(&lt;constant&gt;, &lt;constant expression&gt;) 其中&lt;constant expression&gt; 产生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 CASE WHEN <expression> = <value> THEN NULL ELSE <expression> END,所以是重复的,第二个是这无关紧要,SQL Server可以识别这是同一个表达式使用了两次,并且会计算一次,每次引用相同的结果使用时间。

【讨论】:

  • 太棒了。非常感谢您的帮助!我可能确实会要求 DBA 获得更多“幕后”信息;这取决于我今晚最终感觉有多怪异。 :-) 干杯!
  • 自我注意:开始制定执行计划!我一直在运行跟踪以找出其他程序在做什么,并对我自己的程序进行感觉检查,但我从未真正在执行前查看过它们。所以除了一个很好的答案,这是一个很好的建议。
【解决方案3】:

在我看来,你把一件简单的事情复杂化了。

这个 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

See sql fiddle here

【讨论】:

  • “过度复杂化”或者只是一个有趣的思想练习——正如我的 OP 中提到的,我正在寻找可以让我避免重复子句的东西。我并不是说就查询优化器或类似的东西而言这很重要,但这只是我想要设计的东西。它已经教会了我一些新的技巧和陷阱,所以我认为这是值得的!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-27
  • 2014-10-05
  • 2015-08-03
  • 1970-01-01
  • 2019-10-21
相关资源
最近更新 更多