Il modo migliore per scrivere query SQL che controlla una colonna per valore non NULL o NULL


17

Ho un SP con un parametro che ha NULL come valore predefinito e quindi voglio fare una query come questa:

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

Quanto WHEREsopra controlla sia un valore non NULL sia un valore NULL per @VersionId.

Sarebbe meglio in termini di prestazioni usare invece IFun'istruzione e duplicare la query in una che cerca non-NULL e un'altra per NULL in questo modo? :

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

O Query Optimizer lo rende sostanzialmente lo stesso?

AGGIORNARE:

(Nota: sto usando SQL Server)

(E per quanto ne so, usare a.VersionId = @VersionIdper entrambi i casi non funzionerà, vero?)



In genere utilizzo quanto segue: ISNULL (a.VersionId, @VersionId) = @VersionId
628426

Risposte:


36

Questo modello

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

può essere sostituito con

EXISTS (SELECT column INTERSECT SELECT @argument)

Ciò ti consentirà di abbinare un NULL a un NULL e consentirà al motore di utilizzare un indice in modo columnefficiente. Per un'eccellente analisi approfondita di questa tecnica, vi rimando all'articolo del blog di Paul White:

Dato che ci sono due argomenti nel tuo caso particolare, puoi usare la stessa tecnica di abbinamento con @Blah- in questo modo sarai in grado di riscrivere l'intera clausola WHERE in modo più o meno conciso:

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

Funzionerà rapidamente con un indice attivo (a.Blah, a.VersionId).


O Query Optimizer lo rende sostanzialmente lo stesso?

In questo caso, si. In tutte le versioni (almeno) da SQL Server 2005 in poi, l'ottimizzatore può riconoscere il modello col = @var OR (@var IS NULL AND col IS NULL)e sostituirlo con il ISconfronto corretto . Questo si basa sulla corrispondenza di riscrittura interna, quindi potrebbero esserci casi più complessi in cui ciò non è sempre affidabile.

Nelle versioni di SQL Server dal 2008 SP1 CU5 incluso , è anche possibile utilizzare l' ottimizzazione dell'incorporamento dei parametri tramite OPTION (RECOMPILE), in cui il valore di runtime di qualsiasi parametro o variabile è incorporato nella query come valore letterale prima della compilazione.

Quindi, almeno in larga misura, in questo caso la scelta è una questione di stile, sebbene la INTERSECTcostruzione sia innegabilmente compatta ed elegante.

I seguenti esempi mostrano lo 'stesso' piano di esecuzione per ogni variazione (esclusi valori letterali e riferimenti variabili):

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);
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.