web-dev-qa-db-fra.com

Comment comparer des valeurs qui peuvent toutes deux être nulles est T-SQL

Je veux m'assurer que je n'insère pas de ligne en double dans ma table (par exemple, seule la clé primaire est différente). Tout mon champ autorise NULLS car j'ai décidé null de signifier "toutes les valeurs". En raison des valeurs NULL, l'instruction suivante dans ma procédure stockée ne peut pas fonctionner:

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

puisque NULL = NULL n'est pas vrai.

Comment puis-je vérifier les doublons sans avoir un IF IS instruction NULL pour chaque colonne?

67
srmark

Utilisez l'opérateur INTERSECT .

C'est NULL- sensible et efficace si vous avez un index composite sur tous vos champs:

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

Notez que si vous créez un index UNIQUE sur vos champs, votre vie sera beaucoup plus simple.

47
Quassnoi

Dans le même esprit que @ la réponse d'Eric , mais sans utiliser de 'NULL' symbole.

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

Cela ne sera vrai que si les deux valeurs sont non-NULL, et sont égaux, ou les deux valeurs sont NULL

68
Graeme Job

Utilisez ISNULL :

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

Vous pouvez remplacer 'NULL' Par quelque chose comme 'All Values' S'il est plus logique de le faire.

Il convient de noter qu'avec deux arguments, ISNULL fonctionne de la même manière que COALESCE , que vous pourriez utiliser si vous avez quelques valeurs à tester (par exemple -COALESCE(@IN_MY_FIELD1, @OtherVal, 'NULL')). COALESCE retourne également après le premier non nul, ce qui signifie qu'il est (légèrement) plus rapide si vous vous attendez à ce que MY_FIELD1 soit vide. Cependant, je trouve ISNULL beaucoup plus lisible, c'est pourquoi je l'ai utilisé ici.

32
Eric

J'avais besoin d'une comparaison similaire lors d'une MERGE:

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

Les vérifications supplémentaires visent à éviter de mettre à jour les lignes où toutes les colonnes sont déjà identiques. Pour mes besoins, je voulais que NULL <> anyValue Soit Vrai et NULL <> NULL Soit Faux.

La solution a évolué comme suit:

Premier essai:

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
)

Deuxième essai:

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
)

Troisième tentative (inspirée de @ réponse de THEn ):

WHEN MATCHED AND
(

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

    -- OR ... Repeat for other columns
)

La même logique ISNULL/NULLIF peut être utilisée pour tester l'égalité et l'inégalité:

  • Égalité: ISNULL(NULLIF(A, B), NULLIF(B, A)) IS NULL
  • Inégalité: ISNULL(NULLIF(A, B), NULLIF(B, A)) IS NOT NULL

Voici un SQL-Fiddle montrant comment cela fonctionne http://sqlfiddle.com/#!3/471d60/1

31
WileCau
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 Par rapport à la solution IFNULL/COALESCE. Mais fonctionnera sans avoir à penser à quelle valeur n'apparaîtra pas dans les données qui peuvent être utilisées comme remplacement pour NULL.

20
Shannon Severance

Vous pouvez fusionner chaque valeur, mais c'est un peu grinçant:

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

Vous devez également vous assurer que la valeur coalesced n'est pas une valeur par ailleurs valide dans la colonne en question. Par exemple, s'il était possible que la valeur de MY_FIELD1 soit 'MF1', cela provoquerait de nombreux hits parasites.

10
butterchicken

Et si vous voulez faire une comparaison pour des valeurs qui NE SONT PAS égales? Le simple fait d'utiliser un "NON" devant les comparaisons mentionnées précédemment ne fonctionne pas. Le mieux que j'ai pu trouver est:

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

Vous créez une clé primaire sur vos champs et laissez le moteur appliquer l'unicité. La logique IF EXISTS est de toute façon incorrecte car elle est défectueuse avec les conditions de course.

6
Remus Rusanu

Vous pouvez utiliser SET ANSI_NULLS afin de spécifier le comportement des opérateurs de comparaison Equals (=) et Not Equal To (<>) lorsqu'ils sont utilisés avec des valeurs nulles.

2
drowa

Égale comparaison:

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

pas égal à comparaison: niez simplement la comparaison égal ci-dessus.

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

Est-ce verbeux? Oui, ça l'est. Cependant, il est efficace car il n'appelle aucune fonction. L'idée est d'utiliser un court-circuit dans les prédicats pour s'assurer que l'opérateur égal (=) n'est utilisé qu'avec des valeurs non nulles, sinon null se propagerait dans l'arborescence des expressions.

1
drowa

NULLIF (TARGET.relation_id, SOURCE.app_relation_id) IS NULL Solution simple

0
user3763117

Vous devrez utiliser IS NULL ou ISNULL. Il n'y a vraiment pas de solution.

0
William Edmondson