2009-11-10 13 views
6

Digamos que tiene un procedimiento almacenado y toma un parámetro opcional. Desea utilizar este parámetro opcional en la consulta SQL. Normalmente, esto es como he visto hacer:¿Manera correcta de manejar los filtros cláusula 'opcional' en SQL?

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

Esto parece funcionar bien, sin embargo, provoca una alta cantidad de lecturas lógicas si se ejecuta la consulta con STATISTICS IO ON. También probé la siguiente variante:

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

Y produce el mismo número de lecturas altas. Si convertimos el SQL en una cadena, a continuación, llamar Sp_executesql en él, las lecturas son casi nulas:

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 

¿Estoy loco? ¿Por qué son cláusulas opcionales en las que es difícil encontrar la verdad?

Actualización: Básicamente, estoy preguntando si hay una manera de mantener la sintaxis estándar dentro de un procedimiento almacenado y obtener bajas lecturas lógicas, como lo hace el método sp_ExecuteSql. Me parece completamente loco construir una cadena ... sin mencionar que hace que sea más difícil de mantener, depurar, visualizar ...

+0

Nicholas, consulte el enfoque de unión a continuación para conocer la sintaxis estándar de sql sin sql dinámico. Tengo mucha curiosidad de ver que publique cómo funciona en su escenario ... – chadhoc

+0

@Nicholas: construyendo una consulta como una cadena antes de ejecutarla es ** exactamente ** qué SQL * dinámico *. Es un problema menor depurar, copiar y pegar, deshacerse de la sintaxis de concatenación de cadenas. –

Respuesta

1

Está usando la cláusula "OR" (implícita y explícitamente) en las dos primeras Declaraciones de SQL. El último es un criterio "Y". "O" siempre es más caro que los criterios "Y". No, no estás loco, se debe esperar.

+2

'EXEC sp_executesql' ** does ** almacena en caché el plan de consulta, ya que v2005: http://www.sommarskog.se/dynamic_sql.html#queryplans –

+0

Tiene razón. No me di cuenta de que está usando el parámetro en sp_ExecuteSQL. – mevdiven

+0

Cambié mi respuesta en consecuencia. Gracias. – mevdiven

2

Esta es otra variación de la técnica parámetro opcional:

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

Estoy bastante seguro de que va a tener el mismo problema de rendimiento sin embargo. Si el rendimiento es n. ° 1, es probable que se quede con la lógica de bifurcación y cerca de consultas duplicadas o la creación de cadenas, que es igualmente doloroso en TSQL.

4

Si convertimos el SQL en una cadena, a continuación, llamar Sp_executesql en él, las lecturas son casi nulas ...

  1. Debido a la consulta ya no está evaluando un OR, que como ya puede ver mata la sargabilidad
  2. El plan de consulta se almacena en caché cuando se utiliza sp_executesql; SQL Server no tiene que hacer un análisis sintáctico dura ...

excelente recurso: The Curse & Blessing of Dynamic SQL

Mientras usted está utilizando consultas con parámetros, se debe a salvo de SQL Injection attacks.

0

EDIT: Adición de link to similar question/answer with context as to why the union/if...else approach works better than OR logic (FYI, Remus, quien responde en este enlace, que se utiliza para trabajar en el equipo de SQL Server desarrollar corredor de servicio y otras tecnologías)

cambio del uso de la "o" sintaxis para un enfoque de unión , verá 2 tiene por objeto que debe mantener su lectura lógica recuento de lo más bajo posible:

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 quiere cancelar el duplicar los resultados, utilice una "unión" en lugar de "unión de todo".

EDIT: Demostración que muestra que el optimizador es lo suficientemente inteligente como para descartar la exploración con un valor de la variable nulo 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 primera consulta lee toda la tabla. Esta no es una buena manera de minimizar IO. –

+0

Este es un método más caro que la declaración SQL representada en la pregunta. – mevdiven

+0

Lo siento chicos, pero el optimizador definitivamente NO explorará toda la tabla en la primera consulta, es lo suficientemente inteligente como para excluir la consulta basada en un valor nulo "Y" con la variable. Un ejemplo simple con salida de estadística IO demostrará, ejecutará localmente y revise por su cuenta (tenga en cuenta que si no tiene un índice buscable en ThisField, siempre obtendrá un escaneo debido a la consulta en su contra, por lo tanto, se supone) - He editado la respuesta con la muestra para demostrar - Poner en algunos datos seleccionar \t superior 1000000 \t \t fundido (a.name como varchar (100)) como thisField, molde (newid() como varchar (50)) como myField en \t #DATA desde \t s – chadhoc

-1

cambio del uso de la "o" sintaxis para un enfoque de dos consultas, se le ver 2 planes diferentes que debe mantener su recuento de lecturas lógicas tan bajo como sea posible:

IF @MyOptionalParam is null 
BEGIN 

    SELECT * 
    FROM dbo.MyTableName t1 

END 
ELSE 
BEGIN 

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

END 

Usted necesidad de luchar impulso de su programador para reducir la duplicación aquí. Tenga en cuenta que está solicitando dos planes de ejecución fundamentalmente diferentes y requiere dos consultas para producir dos planes.

+0

Pero, ¿qué sucede si tiene varios parámetros opcionales que desea filtrar? Supongo que no entiendo por qué son "dos planes de ejecución fundamentalmente diferentes". Si soy un analizador, miro la variable, vaya "hey, es nulo y nunca será de otra manera ... puedo dejar de filtrar". Pero supongo que no funciona de esa manera, al menos en SQL 2005. –

+0

Si tiene varios parámetros opcionales, las probabilidades son que solo unas pocas son significativas para el plan de consulta ... simplemente ramifíquelas. En cuanto a la detección de parámetros: http://sqlblog.com/blogs/ben_nevarez/archive/2009/08/27/the-query-optimizer-and-parameter-sniffing.aspx Buena suerte con este enfoque. –

+0

Al infractor sin comentarios: lo entiendo. Usted cree que tiene razón y que también está en la mayoría. Sin embargo, estás equivocado. –

Cuestiones relacionadas