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?
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.
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
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.
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é:
ISNULL(NULLIF(A, B), NULLIF(B, A)) IS NULL
ISNULL(NULLIF(A, B), NULLIF(B, A)) IS NOT NULL
Voici un SQL-Fiddle montrant comment cela fonctionne http://sqlfiddle.com/#!3/471d60/1
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.
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.
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)
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.
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.
É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.
NULLIF (TARGET.relation_id, SOURCE.app_relation_id) IS NULL Solution simple
Vous devrez utiliser IS NULL ou ISNULL. Il n'y a vraiment pas de solution.