1

I have read up on the use of "SET ANSI_NULLS OFF" for a current session to be able to evaluate NULL = NULL to true, e.g. the following example shows the different between ANSI_NULLS ON AND ANSI_NULLS OFF:

QUERY A:

SET ANSI_NULLS OFF

IF(NULL = NULL)
    SELECT 'NULL = NULL'
ELSE 
    SELECT 'NO MATCH'

RESULT: 'NULL = NULL'

QUERY B:

SET ANSI_NULLS ON

IF(NULL = NULL)
    SELECT 'NULL = NULL'
ELSE 
    SELECT 'NO MATCH'

RESULT: 'NO MATCH'

So this shows the difference between the ON and OFF setting.

This also seems to work when using it in a where clause in a standard select statement.

HOWEVER, this does not seem to work in a merge when source and target fields are null.

Reproducing a simple scenario:

CREATE TEST TABLE:

CREATE TABLE [dbo].[TestTable]
(
    [Id] [INT] IDENTITY(1,1) NOT NULL,
    [SomeText] [NVARCHAR](100) NULL,
    [Counter] [INT] NOT NULL,

    CONSTRAINT [PK_TestTable] 
        PRIMARY KEY CLUSTERED ([Id] ASC)
) ON [PRIMARY]
GO

MERGE QUERY

MERGE INTO TestTable AS Target
USING (VALUES(NULL)) AS Source(SomeText) ON Target.SomeText = Source.SomeText

WHEN MATCHED THEN
    UPDATE SET Target.Counter = Target.Counter + 1

WHEN NOT MATCHED THEN
    INSERT (SomeText) VALUES(Source.SomeText);

If matched, the counter is increased by 1. If not, a new row is inserted. When running the query twice, the result is two rows, which is not what I would expect when ansi_nulls are off.

If I change the value NULL til 'test', the match works just fine, e.g.

USING (VALUES(NULL)) => USING (VALUES('test'))

Is there some special behaviour going on when using merges that explains this? Or is it a bug in sql server?

NOTE: I am not looking for a work-around using an ISNULL(...)-solution or something like that. This way I cannot ensure effective use of an index of the matching fields. The original issue is regarding a merge with multiple match fields where multiple of these can happen to be null.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Stephan Møller
  • 1,247
  • 19
  • 39
  • 3
    Idealy don't use `SET ANSI_NULLS OFF`, it's deprecated. As per the [documentation](https://learn.microsoft.com/en-us/sql/t-sql/statements/set-ansi-nulls-transact-sql): *"In a future version of SQL Server, ANSI_NULLS will be ON and any applications that explicitly set the option to OFF will generate an error. Avoid using this feature in new development work, and plan to modify applications that currently use this feature."* What are you actually trying to achieve here? If you need to compare a `NULL` to a `NULL` use `A.Column IS NULL and B.Column IS NULL` – Thom A Dec 21 '18 at 19:26
  • 1
    There are many issues with the MERGE statement, which Aaron Bertrand has documented here: https://www.mssqltips.com/sqlservertip/3074/use-caution-with-sql-servers-merge-statement/ I don't know if your specific issue is covered here, but I'd still encourage you to avoid using MERGE. – Joe Stefanelli Dec 21 '18 at 19:32
  • I am trying to use a MERGE this way: MERGE INTO... ... ON Target.Field1 = Source.Field1 AND Target.Field2 = Source.Field2 AND Target.Field3 = Source.Field3 ... I need this match to support null matches and non-null matches. I know I can do this: If ( (Target.Field1 = Source.Field1) OR (Target.Field1 IS NULL AND Source.Field1 IS NULL) ) @larnu But this way I predict the query plan to not be efficient in all cases of mixes with null and non-null values for field1, field2 and field3. How would you suggest effective matching in this case? – Stephan Møller Dec 21 '18 at 19:33
  • @JoeStefanelli Yes I am aware of this, and just needed this confirmed to be sure before replacing with a simple SELECT + UPDATE/INSERT statement. – Stephan Møller Dec 21 '18 at 19:34
  • Personally, I agree, `MERGE` in SQL Server 2008 had a lot of problems. Many were address in 2012+, but 2008 has it bad. Plus, the combination of `SET ANSI_NULLS OFF` just means "bad" things. And why do you believe that it'll perform poorly? Clauses like `ON A.Column = B.Column OR (A.Column IS NULL AND B.Column)` are quite common when you need to have equality between `NULL`s. That, or if `NULL` represents some kind of value, don't use `NULL`. – Thom A Dec 21 '18 at 19:38
  • 1
    `SET ANSI_NULLS ON affects a comparison only if one of the operands of the comparison is either a variable that is NULL or a literal NULL. If both sides of the comparison are columns or compound expressions, the setting does not affect the comparison.` – Martin Smith Dec 21 '18 at 23:01
  • @MartinSmith thank you for answering the question with the actual explanation instead of just saying the function in question is deprecated, or that one should not use null in merges. Can you post your comment as a real answer? I think this will help other in the future. – Stephan Møller Dec 22 '18 at 07:47

2 Answers2

1

SET ANSI_NULLS only affects the semantics of NULL comparisons in very limited cases. Specifically it

affects a comparison only if one of the operands of the comparison is either a variable that is NULL or a literal NULL. If both sides of the comparison are columns or compound expressions, the setting does not affect the comparison. (source)

When you wrap the literal NULL in a derived table this condition is no longer met so it is not expected that this setting will do as you want.

Martin Smith
  • 438,706
  • 87
  • 741
  • 845
0

Workaround only!

If you want to handle nulls you could change ON condition from Target.SomeText = Source.SomeText to IS NOT DISTINCT FROM equivalent:

MERGE INTO TestTable AS Target
USING (VALUES(NULL)) AS Source(SomeText)
ON EXISTS (SELECT Target.SomeText INTERSECT SELECT Source.SomeText)
WHEN MATCHED THEN
UPDATE SET Target.Counter = ISNULL(Target.Counter,1) + 1
WHEN NOT MATCHED THEN
INSERT (SomeText) VALUES(Source.SomeText);

db<>fiddle demo

I agree with comments that you should avoid using SET ANSI_NULLS OFF because it is deprecated.

Lukasz Szozda
  • 162,964
  • 23
  • 234
  • 275
  • The match must be using index effectively – Stephan Møller Dec 22 '18 at 08:07
  • 2
    @StephanRyer - Have you checked the execution plan to see that it isn't? SQL Server can understand this pattern and do a seek with it. See https://sqlkiwi.blogspot.com/2011/06/undocumented-query-plans-equality-comparisons.html – Martin Smith Dec 24 '18 at 09:22
  • Actually, I checked it and when using explicit index hint it seems to be using the index correctly. Good seek using index: ... WHERE (Column = @ value OR (Column IS NULL AND @value IS NULL)). Bad performance not able to use index: ... WHERE ISNULL(ColumnField, '') = ISNULL(@ value, '') The latter case is the index will be inefficient. – Stephan Møller Jan 07 '19 at 15:14