Die beste Möglichkeit, eine SQL-Abfrage zu schreiben, die eine Spalte auf Nicht-NULL-Werte oder NULL überprüft

17

Ich habe einen SP mit einem Parameter, der NULL als Standardwert hat, und dann möchte ich eine Abfrage wie folgt durchführen:

SELECT ...
FROM ...
WHERE a.Blah = @Blah AND (a.VersionId = @VersionId OR (@VersionId IS NULL AND a.VersionId IS NULL));

Das WHEREobige prüft sowohl auf einen Nicht-NULL-Wert als auch auf einen NULL-Wert für @VersionId.

Wäre es in Bezug auf die Leistung besser, stattdessen eine IFAnweisung zu verwenden und die Abfrage in eine zu duplizieren, die nach Nicht-NULL und eine andere nach NULL sucht? :

IF @VersionId IS NULL BEGIN
    SELECT ...
    FROM ...
    WHERE a.Blah = @Blah AND a.VersionId IS NULL;
ELSE BEGIN
    SELECT ...
    FROM ...
    WHERE a.Blah = @Blah AND a.VersionId = @VersionId;
END

Oder das Abfrageoptimierungsprogramm macht es im Wesentlichen gleich?

AKTUALISIEREN:

(Hinweis: Ich verwende SQL Server.)

(Und soweit ich weiß, a.VersionId = @VersionIdfunktioniert die Verwendung in beiden Fällen nicht, oder?)

user2173353
quelle
2
In Verbindung stehender Artikel: Parameter-Sniffing, Einbettung und die RECOMPILE-Optionen
ypercubeᵀᴹ
Im Allgemeinen verwende ich Folgendes: ISNULL (a.VersionId, @VersionId) = @VersionId
628426

Antworten:

36

Dieses Muster

column = @argument OR (@argument IS NULL AND column IS NULL)

kann durch ersetzt werden

EXISTS (SELECT column INTERSECT SELECT @argument)

Auf diese Weise können Sie eine NULL mit einer NULL abgleichen und die Engine kann einen Index columneffizient verwenden. Für eine hervorragende eingehende Analyse dieser Technik verweise ich Sie auf den Blog-Artikel von Paul White:

Da es in Ihrem speziellen Fall zwei Argumente gibt, können Sie dieselbe Matching-Technik verwenden, mit der @BlahSie die gesamte WHERE-Klausel mehr oder weniger präzise umschreiben können:

WHERE
  EXISTS (SELECT a.Blah, a.VersionId INTERSECT SELECT @Blah, @VersionId)

Dies funktioniert schnell, wenn ein Index aktiviert ist (a.Blah, a.VersionId).


Oder das Abfrageoptimierungsprogramm macht es im Wesentlichen gleich?

In diesem Fall ja. In allen Versionen (mindestens) ab SQL Server 2005 kann das Optimierungsprogramm das Muster erkennen col = @var OR (@var IS NULL AND col IS NULL)und durch den richtigen ISVergleich ersetzen . Dies hängt von der internen Anpassung des Überschreibens ab, sodass es komplexere Fälle geben kann, in denen dies nicht immer zuverlässig ist.

In Versionen von SQL Server ab einschließlich 2008 SP1 CU5 haben Sie auch die Möglichkeit, die Optimierung für das Einbetten von Parametern über zu verwenden OPTION (RECOMPILE), wobei der Laufzeitwert eines beliebigen Parameters oder einer Variablen vor dem Kompilieren als Literal in die Abfrage eingebettet wird.

Zumindest weitgehend ist die Wahl in diesem Fall eine Frage des Stils, obwohl die INTERSECTKonstruktion unbestreitbar kompakt und elegant ist.

Die folgenden Beispiele zeigen den gleichen Ausführungsplan für jede Variation (Literale und variable Referenzen ausgeschlossen):

DECLARE @T AS table
(
    c1 integer NULL,
    c2 integer NULL,
    c3 integer NULL

    UNIQUE CLUSTERED (c1, c2)
);

-- Some data
INSERT @T
    (c1, c2, c3)
SELECT 1, 1, 1 UNION ALL
SELECT 2, 2, 2 UNION ALL
SELECT NULL, NULL, NULL UNION ALL
SELECT 3, 3, 3;

-- Filtering conditions
DECLARE 
    @c1 integer,
    @c2 integer;

SELECT
    @c1 = NULL,
    @c2 = NULL;

-- Writing the NULL-handling out explicitly
SELECT * 
FROM @T AS T
WHERE 
(
    T.c1 = @c1
    OR (@c1 IS NULL AND T.c1 IS NULL)
)
AND 
(
    T.c2 = @c2
    OR (@c2 IS NULL AND T.c2 IS NULL)
);

-- Using INTERSECT
SELECT * 
FROM @T AS T
WHERE EXISTS 
(
    SELECT T.c1, T.c2 
    INTERSECT 
    SELECT @c1, @c2
);

-- Using separate queries
IF @c1 IS NULL AND @c2 IS NULL
    SELECT * 
    FROM @T AS T
    WHERE T.c1 IS NULL
    AND T.c2 IS NULL
ELSE IF @c1 IS NULL
    SELECT * 
    FROM @T AS T
    WHERE T.c1 IS NULL
    AND T.c2 = @c2
ELSE IF @c2 IS NULL
    SELECT * 
    FROM @T AS T
    WHERE T.c1 = @c1
    AND T.c2 IS NULL
ELSE
    SELECT * 
    FROM @T AS T
    WHERE T.c1 = @c1
    AND T.c2 = @c2;

-- Using OPTION (RECOMPILE)
-- Requires 2008 SP1 CU5 or later
SELECT * 
FROM @T AS T
WHERE 
(
    T.c1 = @c1
    OR (@c1 IS NULL AND T.c1 IS NULL)
)
AND 
(
    T.c2 = @c2
    OR (@c2 IS NULL AND T.c2 IS NULL)
)
OPTION (RECOMPILE);
Andriy M
quelle