2009-11-10 6 views
6

Supponiamo che tu abbia una stored procedure e che abbia un parametro opzionale. Si desidera utilizzare questo parametro facoltativo nella query SQL. In genere questo è come ho visto fare:Modo corretto per gestire 'opzionale' dove la clausola filtra in SQL?

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

Questo sembra funzionare bene, tuttavia provoca una quantità elevata di letture logiche se si esegue la query con STATISTICS IO ON. Ho anche provato la seguente variante:

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

E produce lo stesso numero di letture alte. Se convertiamo lo SQL in una stringa, quindi chiamare sp_executesql su di esso, le letture sono quasi nulle:

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 

Sono pazzo? Perché le clausole opzionali sono così difficili da ottenere?

Aggiornamento: Sto praticamente chiedendo se c'è un modo per mantenere la sintassi standard all'interno di una stored procedure e ottenere letture logiche basse, come fa il metodo sp_ExecuteSql. Mi sembra completamente pazzo costruire una stringa ... per non parlare rende più difficile mantenere, eseguire il debug, visualizzare ..

+0

Nicholas, vedi l'approccio unione qui sotto per un modo di utilizzare la sintassi sql standard senza sql dinamico - Sarei molto curioso di vederti pubblicare sul suo rendimento nel tuo scenario ... – chadhoc

+0

@Nicholas: Costruire una query come una stringa prima dell'esecuzione è ** esattamente ** che cos'è * dynamic * SQL. È un problema secondario per il debug: copia/incolla, eliminazione della sintassi della concatenazione delle stringhe. –

risposta

1

Stai usando la clausola "OR" (implicitamente ed esplicitamente) sui primi due Istruzioni SQL. L'ultimo è un criterio "AND". "OR" è sempre più costoso dei criteri "AND". No, non sei pazzo, dovrebbe essere previsto.

+2

'EXEC sp_executesql' ** ** memorizza nella cache il piano di query, poiché v2005: http://www.sommarskog.se/dynamic_sql.html#queryplans –

+0

Hai ragione. Non ho notato che sta usando il parametro su sp_ExecuteSQL. – mevdiven

+0

Modificata la mia risposta di conseguenza. Grazie. – mevdiven

2

Questa è un'altra variante della tecnica parametro opzionale:

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

Sono abbastanza sicuro che avrà lo stesso problema di prestazioni però. Se la performance è # 1, probabilmente rimarrai bloccato con la logica di fork e con la duplicazione di query o stringhe che è altrettanto doloroso in TSQL.

4

Se convertiamo lo SQL in una stringa, quindi chiamare sp_executesql su di essa, la legge sono quasi nullo ...

  1. Perché la query non è più la valutazione di un OR, che come può vedere uccide sargability
  2. Il piano di query viene memorizzato nella cache quando si utilizza sp_executesql; SQL Server non ha bisogno di fare un parse difficile ...

eccellente risorsa: The Curse & Blessing of Dynamic SQL

Finché si utilizza query con parametri, si dovrebbe al sicuro da SQL Injection attacks.

0

EDIT: Aggiunta di link to similar question/answer with context as to why the union/if...else approach works better than OR logic (FYI, Remus, ha risposto in questo link, abituato a lavorare sul team di SQL Server di sviluppo broker di servizio e altre tecnologie)

Cambio di utilizzare il "o" sintassi per un approccio unione , vedrete 2 cerca che dovrebbe tenere contano più basso possibile la vostra lettura logica:

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 

Se si vuole de-duplicare i risultati, utilizzare una "unione" invece di "unione tutti".

EDIT: Demo dimostrando che l'ottimizzatore è abbastanza intelligente per escludere la scansione con un valore variabile null in 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 prima query legge l'intera tabella. Questo non è un buon modo per minimizzare l'IO. –

+0

Questo è un metodo più costoso dell'istruzione SQL descritta nella domanda. – mevdiven

+0

Scusate ragazzi, ma l'ottimizzatore NON scriverà l'intera tabella nella prima query, è abbastanza intelligente da escludere la query basata su un valore nullo "AND" con la variabile.Verrà mostrato un semplice esempio con output di IO stat localmente e rivedi da solo (nota che se non hai un indice cercabile su ThisField, otterrai sempre una scansione a causa della query su di esso, quindi si presume) - Ho modificato la risposta con il campione per dimostrare - Mettere in alcuni dati selezionare \t top 1000000 \t \t cast (a.name come varchar (100)), come thisField, cast (newid() come varchar (50)) come myField in \t #data da \t s – chadhoc

-1

Cambio di utilizzare il "o" sintassi per un approccio in due query, è ll vedi 2 piani diversi che dovrebbero mantenere il valore del tuo lettura logica il più basso possibile:

IF @MyOptionalParam is null 
BEGIN 

    SELECT * 
    FROM dbo.MyTableName t1 

END 
ELSE 
BEGIN 

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

END 

Hai bisogno di combattere stimolo del programmatore per ridurre la duplicazione qui. Renditi conto che stai chiedendo due piani di esecuzione fondamentalmente diversi e richiedi due query per produrre due piani.

+0

Ma se si dispone di più parametri facoltativi da filtrare? Immagino di non capire perché siano "due piani di esecuzione fondamentalmente diversi". Se sono un parser, guardo la variabile, vai "hey, è null e non sarà mai diversamente .. Posso smettere di filtrare su di esso." Ma suppongo che non funzioni in questo modo, almeno in SQL 2005. –

+0

Se si dispone di più parametri facoltativi, le probabilità sono che solo poche sono significative per il piano di query ... basta ramificarle. Per quanto riguarda lo sniffing dei parametri: http://sqlblog.com/blogs/ben_nevarez/archive/2009/08/27/the-query-optimizer-and-parameter-sniffing.aspx Buona fortuna con questo approccio. –

+0

Al downvoter senza commenti: ho capito. Credi di avere ragione e anche tu sei nella maggioranza. Tuttavia, ti sbagli. –

Problemi correlati