2009-11-10 7 views
6

Supposons que vous ayez une procédure stockée et qu'elle prenne un paramètre facultatif. Vous souhaitez utiliser ce paramètre facultatif dans la requête SQL. En général, c'est ainsi que je l'ai vu faire:Une bonne façon de gérer 'facultatif' où les filtres clause clause dans SQL?

SELECT * FROM dbo.MyTableName t1 
WHERE t1.ThisField = 'test' 
AND (@MyOptionalParam IS NULL OR t1.MyField = @MyOptionalParam) 

Cela semble bien fonctionner, mais il provoque une grande quantité de lectures logiques si vous exécutez la requête avec STATISTIQUES IO. J'ai également essayé la variante suivante:

SELECT * FROM dbo.MyTableName t1 
WHERE t1.ThisField = 'test' 
AND t1.MyField = CASE WHEN @MyOptionalParam IS NULL THEN t1.MyField ELSE @MyOptionalParam END 

Et il donne le même nombre de lectures élevées. Si nous convertissons le SQL à une chaîne, puis appelez sp_executesql à ce sujet, les lectures sont presque nulles:

DECLARE @sql nvarchar(max) 

SELECT @sql = 'SELECT * FROM dbo.MyTableName t1 
WHERE t1.ThisField = ''test''' 

IF @MyOptionalParam IS NOT NULL 
BEGIN 
    SELECT @sql = @sql + ' AND t1.MyField = @MyOptionalParam ' 
END 

EXECUTE sp_ExecuteSQL @sql, N'@MyOptionalParam', @MyOptionalParam 

Suis-je fou? Pourquoi les clauses facultatives sont-elles si difficiles à obtenir?

Mise à jour: Je demande essentiellement s'il existe un moyen de conserver la syntaxe standard dans une procédure stockée et d'obtenir des lectures logiques faibles, comme le fait la méthode sp_ExecuteSql. Il me semble complètement fou de construire une chaîne ... sans compter que cela rend plus difficile à maintenir, déboguer, visualiser ..

+0

Nicholas, voir l'approche de l'union ci-dessous pour un moyen d'utiliser la syntaxe SQL standard sans SQL dynamique - Je serais très curieux de vous voir comment il fonctionne dans votre scénario ... – chadhoc

+0

@Nicholas: Construire une requête comme une chaîne avant de l'exécuter est ** exactement ** ce que * dynamic * SQL est. C'est un problème mineur à déboguer - copier/coller, se débarrasser de la syntaxe de concaténation de chaîne. –

Répondre

1

Vous utilisez la clause "OR" (implicitement et explicitement) sur les deux premiers Instructions SQL Le dernier est un critère "ET". "OU" est toujours plus cher que "ET" critères. Non, vous n'êtes pas fou, devrait être prévu.

+2

'EXEC sp_executesql' ** met en cache le plan de requête, car v2005: http://www.sommarskog.se/dynamic_sql.html#queryplans –

+0

Vous avez raison. Je n'ai pas remarqué qu'il utilise un paramètre sur sp_ExecuteSQL. – mevdiven

+0

Modifié ma réponse en conséquence. Merci. – mevdiven

2

C'est une autre variante de la technique de paramètre optionnel:

SELECT * FROM dbo.MyTableName t1 
WHERE t1.ThisField = 'test' 
AND t1.MyField = COALESCE(@MyOptionalParam, t1.MyField) 

Je suis sûr que ce sera le même problème de performance bien. Si la performance est n ° 1, vous serez probablement bloqué avec la logique de forking et les requêtes en double ou les chaînes de construction qui sont tout aussi douloureuses dans TSQL.

4

Si nous convertissons le SQL à une chaîne, puis appelez sp_executesql à ce sujet, les lectures sont presque nulles ...

  1. Parce que votre requête n'évalue plus un OU, qui, comme vous peut voir tuer la sargabilité
  2. Le plan de requête est mis en cache lors de l'utilisation de sp_executesql; SQL Server ne doit pas faire une analyse syntaxique dur ...

Excellente adresse: The Curse & Blessing of Dynamic SQL

Tant que vous utilisez des requêtes paramétrées, vous devriez en toute sécurité à partir SQL Injection attacks.

0

EDIT: Ajout link to similar question/answer with context as to why the union/if...else approach works better than OR logic (Pour votre information, Remus, le answerer dans ce lien, utilisé pour travailler sur l'équipe SQL Server développement courtier de services et d'autres technologies)

Changement d'utiliser la syntaxe « ou » à une approche syndicale , vous verrez 2 cherche qui devrait garder votre lecture logique compte aussi bas que possible:

SELECT * FROM dbo.MyTableName t1 
WHERE t1.ThisField = 'test' 
AND @MyOptionalParam IS NULL 
union all 
SELECT * FROM dbo.MyTableName t1 
WHERE t1.ThisField = 'test' 
AND t1.MyField = @MyOptionalParam 

Si vous voulez de-dupliquer les résultats, utilisez une « union » au lieu de « union tous ».

EDIT: Demo montrant que l'optimiseur est assez intelligent pour exclure de balayage avec une valeur variable nulle en UNION:

if object_id('tempdb..#data') > 0 
    drop table #data 
go 

-- Put in some data 
select top 1000000 
     cast(a.name as varchar(100)) as thisField, cast(newid() as varchar(50)) as myField 
into #data 
from sys.columns a 
cross join sys.columns b 
cross join sys.columns c; 
go 

-- Shwo count 
select count(*) from #data; 
go 

-- Index on thisField 
create clustered index ixc__blah__temp on #data (thisField); 
go 

set statistics io on; 
go 

-- Query with a null parameter value 
declare @MyOptionalParam varchar(50); 
select * 
from #data d 
where d.thisField = 'test' 
and  @MyOptionalParam is null; 
go 

-- Union query 
declare @MyOptionalParam varchar(50); 
select * 
from #data d 
where d.thisField = 'test' 
and  @MyOptionalParam is null 
union all 
select * 
from #data d 
where d.thisField = 'test' 
and  d.myField = '5D25E9F8-EA23-47EE-A954-9D290908EE3E'; 
go 

-- Union query with value 
declare @MyOptionalParam varchar(50); 
select @MyOptionalParam = '5D25E9F8-EA23-47EE-A954-9D290908EE3E' 
select * 
from #data d 
where d.thisField = 'test' 
and  @MyOptionalParam is null 
union all 
select * 
from #data d 
where d.thisField = 'test' 
and  d.myField = '5D25E9F8-EA23-47EE-A954-9D290908EE3E'; 
go 

if object_id('tempdb..#data') > 0 
    drop table #data 
go 
+0

La première requête lit la table entière. Ce n'est pas un bon moyen de minimiser les entrées/sorties. –

+0

Cette méthode est plus coûteuse que l'instruction SQL décrite dans la question. – mevdiven

+0

Désolé les gars, mais l'optimiseur ne va certainement pas analyser toute la table dans la première requête, il est assez intelligent pour exclure la requête basée sur une valeur nulle "AND" avec la variable.Un exemple simple avec sortie IO stat va démontrer, exécuter localement et vérifiez vous-même (notez que si vous n'avez pas d'index de recherche sur ThisField, vous obtiendrez toujours un scan en raison de la requête, donc cela est supposé) - J'ai édité la réponse avec l'échantillon démontrer - Mettre dans certaines données sélectionner \t haut 1000000 \t \t coulée (a.name comme varchar (100)) en tant que CeChamp, coulée (newid() comme varchar (50)) en tant que myField dans \t #data à partir de \t s – chadhoc

-1

Changement d'utiliser la syntaxe « ou » à une approche à deux de requête, vous aurez voir 2 plans différents qui devraient garder votre compte de lecture logique aussi bas que possible:

IF @MyOptionalParam is null 
BEGIN 

    SELECT * 
    FROM dbo.MyTableName t1 

END 
ELSE 
BEGIN 

    SELECT * 
    FROM dbo.MyTableName t1 
    WHERE t1.MyField = @MyOptionalParam 

END 

Vous devez combattre l'envie de votre programmeur pour réduire le double emploi ici. Réalisez que vous demandez deux plans d'exécution fondamentalement différents et que vous avez besoin de deux requêtes pour produire deux plans.

+0

Mais que se passe-t-il si vous avez plusieurs paramètres optionnels que vous souhaitez filtrer? Je suppose que je ne comprends pas pourquoi c'est "deux plans d'exécution fondamentalement différents". Si je suis un analyseur, je regarde la variable, allez "hé, c'est nul et ne sera jamais autrement .. Je peux arrêter de filtrer dessus." Mais je suppose que cela ne fonctionne pas de cette façon, au moins dans SQL 2005. –

+0

Si vous avez plusieurs paramètres optionnels, il y a de fortes chances que seulement quelques-uns soient significatifs pour le plan de requête ... branchez-les simplement. En ce qui concerne le reniflage des paramètres: http://sqlblog.com/blogs/ben_nevarez/archive/2009/08/27/the-query-optimizer-and-parameter-sniffing.aspx Bonne chance pour cette approche. –

+0

Pour le downvoter sans commentaires - Je comprends. Vous croyez avoir raison et vous êtes également majoritaire. Cependant, vous avez tort. –