如何比较在T-SQL中可能都为空的值


73

我想确保我没有在表中插入重复的行(例如,仅主键不同)。我所有的字段都允许使用NULLS,因为我将null表示为“所有值”。由于为空,因此我的存储过程中的以下语句不起作用:

IF EXISTS(SELECT * FROM MY_TABLE WHERE 
    MY_FIELD1 = @IN_MY_FIELD1  AND
    MY_FIELD2 = @IN_MY_FIELD2  AND
    MY_FIELD3 = @IN_MY_FIELD3  AND 
    MY_FIELD4 = @IN_MY_FIELD4  AND
    MY_FIELD5 = @IN_MY_FIELD5  AND
    MY_FIELD6 = @IN_MY_FIELD6)
    BEGIN
        goto on_duplicate
    END

因为NULL = NULL不为真。

如何在没有每一列都具有IF IS NULL语句的情况下检查重复项?


MY_FIELD{1,6}(从表中)和变量@IN_MY_FILED{1,6}nullables还是仅仅是MY_FIELD{1,6}
支石墓

这个答案是有关的:stackoverflow.com/a/12456786/952135。另外这篇文章:modern-sql.com/feature/is-distinct-from
Oliv

Answers:


51

使用INTERSECT运算符。

NULL如果您在所有字段上都有一个复合索引,则它是敏感且高效的:

IF      EXISTS
        (
        SELECT  MY_FIELD1, MY_FIELD2, MY_FIELD3, MY_FIELD4, MY_FIELD5, MY_FIELD6
        FROM    MY_TABLE
        INTERSECT
        SELECT  @IN_MY_FIELD1, @IN_MY_FIELD2, @IN_MY_FIELD3, @IN_MY_FIELD4, @IN_MY_FIELD5, @IN_MY_FIELD6
        )
BEGIN
        goto on_duplicate
END

请注意,如果您UNIQUE在字段上创建索引,那么您的生活将会简单得多。


1
注意INTERSECT仅从SQLServer 2005起可用。我们中有些人落后了几年:)
butterchicken

2
当此方法用作单个字段的部分连接谓词时,它的执行要比等效的“(a = b或(a为null且b为null))”慢得多。字段a和b恰好是sql_variants,也许这就是原因。
crokusek'1

81

@Eric的答案相同,但不使用'NULL'符号。

(Field1 = Field2) OR (ISNULL(Field1, Field2) IS NULL)

仅当两个值都是non-NULL,并且彼此相等,或者两个值都是NULL


8
对于Equality测试,这绝对是最严格的测试(A = B OR ISNULL(A, B) IS NULL没有不必要的限制),但是由于UNKNOWN无法反转的方式,因此反转并不安全。我见过的最短的不平等测试是@WileCau。
tovodeverett

这将有问题的索引
Royi纳米尔

是的,但是如果一个或两个值均为NULL,则它将返回NULL。如果一个或两个值都为null,则我们希望返回的结果为false
Pxtl

41

进行合并时,我需要进行类似的比较:

WHEN MATCHED AND (Target.Field1 <> Source.Field1 OR ...)

额外的检查是为了避免更新所有列均已相同的行。为了我的目的,我想NULL <> anyValue成为真实,而NULL <> NULL成为虚假。

解决方案演变如下:

第一次尝试:

WHEN MATCHED AND
(
    (
        -- Neither is null, values are not equal
        Target.Field1 IS NOT NULL
            AND Source.Field1 IS NOT NULL
            AND Target.Field1 <> Source.Field1
    )
    OR
    (
        -- Target is null but source is not
        Target.Field1 IS NULL
            AND Source.Field1 IS NOT NULL
    )
    OR
    (
        -- Source is null but target is not
        Target.Field1 IS NOT NULL
            AND Source.Field1 IS NULL
    )

    -- OR ... Repeat for other columns
)

第二次尝试:

WHEN MATCHED AND
(
    -- Neither is null, values are not equal
    NOT (Target.Field1 IS NULL OR Source.Field1 IS NULL)
        AND Target.Field1 <> Source.Field1

    -- Source xor target is null
    OR (Target.Field1 IS NULL OR Source.Field1 IS NULL)
        AND NOT (Target.Field1 IS NULL AND Source.Field1 IS NULL)

    -- OR ... Repeat for other columns
)

第三次尝试(受@THEn的回答启发):

WHEN MATCHED AND
(

    ISNULL(
        NULLIF(Target.Field1, Source.Field1),
        NULLIF(Source.Field1, Target.Field1)
    ) IS NOT NULL

    -- OR ... Repeat for other columns
)

可以使用相同的ISNULL / NULLIF逻辑来测试相等和不相等:

  • 平等: ISNULL(NULLIF(A, B), NULLIF(B, A)) IS NULL
  • 不平等: ISNULL(NULLIF(A, B), NULLIF(B, A)) IS NOT NULL

这是一个SQL小提琴,演示了它的工作方式http://sqlfiddle.com/#!3/471d60/1


1
在GoogleNULLIF(A, B) IS NULL AND NULLIF(B, A) IS NULL搜寻之前,我曾尝试解决此问题,并设法提出(为了实现平等),而不是为了不平等而徒劳无措。对于Equality,您的字符要短3个字符,对于不相等的字符,您的短4个字符,但我认为我的理解会稍微容易一些(尽管在这种情况下这绝对是相对的)。
tovodeverett

1
这是很难理解的,因此最好将ISNULLed与不可能的值进行比较,但这有时不是解决方案(例如,在比较NULLable位列时)。如果有内置的解决方案肯定会很好,但是我认为有某种意识形态的动机将这种事情排除在T-SQL之外,以防止人们以“错误”的方式使用NULL。
Tmdean

1
我认为INTERSECT和EXCEPT提供了很好的方法来进行相等/不相等搜索,因为NULL作为值进行处理...
HansLindgren 2015年

2
很好的答案,但是对我来说,它证明了三值逻辑对于现实世界的使用是多么不可原谅的可恶,至少没有比任何SQL方言都多的运算符。
-Pxtl

@Pxtl ISO SQL具有IS DISTINCT FROMIS NOT DISTINCT FROM运算符(modern-sql.com/feature/is-distinct-from)-但是太冗长了,SQL Server仍然不支持它。我真的很喜欢MySQL对<=>for的使用IS DISTINCT FROM-尽管它没有简洁的IS NOT DISTINCT FROM语法。
戴军

33

用途ISNULL

ISNULL(MY_FIELD1, 'NULL') = ISNULL(@IN_MY_FIELD1, 'NULL')

如果这样做更有意义,则可以更改'NULL'为类似的'All Values'内容。

应该注意的是,有两个参数,其ISNULL工作原理与相同COALESCE,如果您有几个要测试的值(即- COALESCE(@IN_MY_FIELD1, @OtherVal, 'NULL')),则可以使用它。COALESCE也会在第一个非空值之后返回,这意味着(如果您希望MY_FIELD1为空白,则(略微)快一些)。但是,我发现ISNULL它更具可读性,所以这就是我在这里使用它的原因。


9
如果该字段的值为'NULL'怎么办?
Remus Rusanu 2009年

2
使用一个不可能出现的替代值,例如数字字段为'x'或字符串字段的字符串太长。
2009年

@Remus:我正在使用替代值。显然,它必须是字段中唯一的未使用值。
Eric

2
@ l0b0使用太长的字符串将不起作用,因为ISNULL会截断替换值以匹配检查值的大小。
Aaroninus

1
:这可能会导致问题詹妮弗空gizmodo.com/...
保罗戴维斯ŧ

20
IF EXISTS(SELECT * FROM MY_TABLE WHERE 
            (MY_FIELD1 = @IN_MY_FIELD1 
                     or (MY_FIELD1 IS NULL and @IN_MY_FIELD1 is NULL))  AND
            (MY_FIELD2 = @IN_MY_FIELD2 
                     or (MY_FIELD2 IS NULL and @IN_MY_FIELD2 is NULL))  AND
            (MY_FIELD3 = @IN_MY_FIELD3 
                     or (MY_FIELD3 IS NULL and @IN_MY_FIELD3 is NULL))  AND
            (MY_FIELD4 = @IN_MY_FIELD4 
                     or (MY_FIELD4 IS NULL and @IN_MY_FIELD4 is NULL))  AND
            (MY_FIELD5 = @IN_MY_FIELD5 
                     or (MY_FIELD5 IS NULL and @IN_MY_FIELD5 is NULL))  AND
            (MY_FIELD6 = @IN_MY_FIELD6
                     or (MY_FIELD6 IS NULL and @IN_MY_FIELD6 is NULL)))
            BEGIN
                    goto on_duplicate
            END

Wordy与IFNULL / COALESCE解决方案相比。但是无需考虑什么值将不会出现在可用作NULL的数据中就可以了。


10

您可以合并每个值,但这有点引起退缩:

    IF EXISTS(SELECT * FROM MY_TABLE WHERE 
    coalesce(MY_FIELD1,'MF1') = coalesce(@IN_MY_FIELD1,'MF1')  AND
    ...
    BEGIN
            goto on_duplicate
    END

您还需要确保该coalesced值不是所讨论列上的其他有效值。例如,如果MY_FIELD1的值可能为'MF1',那么这将导致很多虚假命中。


7

如果要比较不相等的值怎么办?仅在前面提到的比较前面使用“ NOT”不起作用。我能想到的最好的是:

(Field1 <> Field2) OR (NULLIF(Field1, Field2) IS NOT NULL) OR (NULLIF(Field2, Field1) IS NOT NULL)

6

您在字段上创建一个主键,并让引擎强制执行唯一性。无论如何,如果存在竞争条件,那么执行IF EXISTS逻辑是不正确的。


1
还是一个独特的约束,因为他指出已经有一个主键
-bdukes

5

等于比较:

((f1 IS NULL AND f2 IS NULL) OR (f1 IS NOT NULL AND f2 IS NOT NULL AND f1 = f2))

不等于比较:只需否定上面的等于比较。

NOT ((f1 IS NULL AND f2 IS NULL) OR (f1 IS NOT NULL AND f2 IS NOT NULL AND f1 = f2))

冗长吗?是的。但是它很有效,因为它不调用任何函数。想法是在谓词中使用短路,以确保仅对非null值使用等号运算符(=),否则null将在表达式树中向上传播。


如此冗长,这是最好的答案。许多其他答案返回NULL而不是FALSE,因此,如果否定,它将为TRUE。
Pxtl

2

可以使用SET ANSI_NULLS以指定当它们与空值所用的等于(=)并且不等于(<>)比较运算符的行为。


1

上面,@ drowa显示了我同意的详细方法。很好,因为它避免了三值逻辑问题。当取消,因为他们正把许多在这里提供的其他方法将在微妙和意想不到的方式不能null等同于false是不是

但是,我有一个使它变得方便的工作流程,这是一个正则表达式。给定形式的代码

(leftSide <=> rightSide)

正则表达式找到此:

\(([a-zA-Z0-9_.@]+)\s*<=>\s*([a-zA-Z0-9_.@]+)\)

并替换为:

(/*$1 <=> $2*/ ($1 IS NULL AND $2 IS NULL) OR ($1 IS NOT NULL AND $2 IS NOT NULL AND $1 = $2))

因此,我编写了(leftSide <=> rightSide)代码,并应用了上述正则表达式转换来获取扩展形式。如果MSSQL提供某种宏扩展,那就更好了,这样我就不必手动进行操作了,但事实并非如此。

引用@Drowa的答案以供参考:

等于比较:

((f1 IS NULL AND f2 IS NULL) OR (f1 IS NOT NULL AND f2 IS NOT NULL AND f1 = f2))

不等于比较:只需否定上面的等于比较。

NOT ((f1 IS NULL AND f2 IS NULL) OR (f1 IS NOT NULL AND f2 IS NOT NULL AND f1 = f2))

冗长吗?是的。但是它很有效,因为它不调用任何函数。想法是在谓词中使用短路,以确保仅对非null值使用等号运算符(=),否则null将在表达式树中向上传播。



0

NULLIF(TARGET.relation_id,SOURCE.app_relation_id)IS NULL简单解决方案


4
如果第一个值为null,但第二个不是,则此方法不起作用。
Aaroninus

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.