2009-12-30 13 views
12

OK, la enésima pregunta de la columna condicional:¿Cómo se filtra condicionalmente una columna en una cláusula WHERE?

Estoy escribiendo un proceso almacenado que toma un parámetro de entrada que está mapeado a una de varias columnas de indicadores. ¿Cuál es la mejor manera de filtrar en la columna solicitada? Actualmente estoy en SQL2000, pero estoy a punto de pasar a SQL2008, así que tomaré una solución contemporánea si hay una disponible.

La tabla consultada en el sproc parece

ID ... fooFlag barFlag bazFlag quuxFlag 
--  ------- ------- ------- -------- 
01   1  0  0   1 
02   0  1  0   0 
03   0  0  1   1 
04   1  0  0   0 

y quiero hacer algo como

select ID, name, description, ... 
from myTable 
where (colname like @flag + 'Flag') = 1 

así que si yo llamo el sproc como exec uspMyProc @flag = 'foo' me vuelva filas 1 y 4

Sé que no puedo hacer la parte en parens directamente en SQL. Para hacer SQL dinámico, tendré que rellenar toda la consulta en una cadena, concatenar el parámetro @flag en la cláusula WHERE y luego ejecutar la cadena. Aparte de la sensación sucia que tengo al hacer SQL dinámico, mi consulta es bastante grande (estoy seleccionando un par de docenas de campos, uniendo 5 tablas, llamando a un par de funciones), por lo que es una gran cadena gigante, todo por una sola línea en un filtro WHERE de 3 líneas.

Como alternativa, podría tener 4 copias de la consulta y seleccionar entre ellas en una declaración CASE. Esto deja el código SQL directamente ejecutable (y sujeto a hilighting de sintaxis, etc.) pero a costa de repetir grandes fragmentos de código, ya que no puedo usar CASE solo en la cláusula WHERE.

¿Hay alguna otra opción? ¿Alguna complicada unión u operación lógica que se pueda aplicar? ¿O debería superarlo y ejecutar el SQL dinámico?

+1

"exec uspMyProc @flag = 'foo' Volvería a la fila 1". ¿Por qué solo la fila 1 y no también la fila 4? –

+0

@Mark - d'oh, sí, arreglado! – Val

Respuesta

18

Hay algunas maneras de hacer esto:

Usted puede hacer esto con una declaración de caso.

select ID, name, description, ... 
from myTable 
where CASE 
    WHEN @flag = 'foo' then fooFlag 
    WHEN @flag = 'bar' then barFlag 
END = 1 

Puede usar IF.

IF (@flag = 'foo') BEGIN 
    select ID, name, description, ... 
    from myTable 
    where fooFlag = 1 
END ELSE IF (@flag = 'bar') BEGIN 
    select ID, name, description, ... 
    from myTable 
    where barFlag = 1 
END 

.... 

Puede tener una cláusula where complicada con muchos paréntesis.

select ID, name, description, ... 
from myTable 
where (@flag = 'foo' and fooFlag = 1) 
OR (@flag = 'bar' and barFlag = 1) OR ... 

Usted puede hacer esto con SQL dinámico:

DECLARE @SQL nvarchar(4000) 

SELECT @SQL = N'select ID, name, description, ... 
from myTable 
where (colname like ''' + @flag + 'Flag'') = 1' 

EXECUTE sp_ExecuteSQL @SQL, N'' 

Hay más, pero creo que uno de ellos se te va.

+0

+1 - Personalmente utilizaría el enfoque 'CASE' porque creo que es el más adecuado, aunque es más probable que use un caso simple en lugar de un caso buscado, ya que lo que ha demostrado es constante (es decir, 'CASE @flag WHEN 'foo' THEN fooFlag CUANDO 'bar' luego barFlag END = 1') –

+0

@Greg: Por supuesto, es probable que tenga el peor rendimiento de todas las opciones, ya que ahora no solo está envolviendo una columna en una función, los está envolviendo a todos. Si tiene un índice diferente en cada columna, esto se convierte en un escaneo de tabla. – Aaronaught

+0

+1 - según la complejidad de la consulta. Aunque en mi experiencia, incluso cuando la consulta comienza de manera simple, siempre termina (durante el mantenimiento, etc.) compleja, y deseé haber comenzado con sql dinámico. – Russell

4

"Como alternativa, podría tener 4 copias de la consulta y seleccionar entre ellas en una declaración CASE".

No es necesario copiar toda la consulta 4 veces, sólo tiene que añadir todas las posibilidades en las cláusulas donde en su única copia de la consulta:

select ID, name, description, ... 
from myTable 
where (@flag = 'foo' and fooFlag = 1) OR (@flag = 'bar' and barFlag = 1) OR ... 
+1

+1: Ese sería mi enfoque, aunque igual usaría la dinámica para que no se arrastren todas las RUP cada vez que se ejecuta la consulta. –

+0

sí, esta es mi solución preferida. Elegí la respuesta de Gabriel, aunque me dio tantas opciones, incl. éste. – Val

0

usted podría tener un parámetro para cada posible bandera columna, luego verifique si el parámetro es nulo o si el valor en la columna es igual al parámetro. Luego, pasa un 1 para las banderas que desea marcar y deja las otras nulas.

select id, name, description, ... 
from myTable 
where (@fooFlag is null or fooFlag = @fooFlag) AND 
     (@barFlag is null or barFlag = @barFlag) AND 
     ... 

Honestamente, sin embargo, esto parece ser un candidato ideal para la construcción de una consulta LINQ dinámica y sin esperar sproc una vez que llegue a SQL2008.

+0

Esto es lo que haría si el rendimiento no fuera una preocupación importante (es decir, las columnas de bandera no están indexadas). – Aaronaught

+0

No, realmente me gustaría evitar params múltiples - más mantenimiento si cuando amplío a más banderas. Con un solo parámetro, el mantenimiento está restringido al SP; si agrego parámetros, también tengo que cambiar la estructura del código de llamada, en lugar de simplemente pasar nuevos valores en un parámetro existente. Gracias, ¡gracias! – Val

+0

Oh, sí, olvidé mencionar, aunque estoy moviendo el DB a SQL2008, mi aplicación es ASP Classic, y no voy a tener la oportunidad de mover esta parte de la aplicación a .NET pronto. Entonces LINQ está fuera ... – Val

3

Lo que haría es CASE algunas variables al principio. Ejemplo:

DECLARE 
    @fooFlag int, 
    @barFlag int, 
    @bazFlag int, 
    @quuxFlag int 

SET @fooFlag = CASE WHEN @flag = 'foo' THEN 1 ELSE NULL END 
SET @barFlag = CASE WHEN @flag = 'bar' THEN 1 ELSE NULL END 
SET @bazFlag = CASE WHEN @flag = 'baz' THEN 1 ELSE NULL END 
SET @quuxFlag = CASE WHEN @flag = 'quux' THEN 1 ELSE NULL END 

SELECT ID, name, description, ... 
FROM myTable 
WHERE (fooFlag >= ISNULL(@fooFlag, 0) AND fooFlag <= ISNULL(@fooFlag, 1)) 
AND (barFlag >= ISNULL(@barFlag, 0) AND barFlag <= ISNULL(@barFlag, 1)) 
AND (bazFlag >= ISNULL(@bazFlag, 0) AND bazFlag <= ISNULL(@bazFlag, 1)) 
AND (quuxFlag >= ISNULL(@quuxFlag, 0) AND quuxFlag <= ISNULL(@quuxFlag, 1)) 

Lo bueno de esta consulta es que, debido a que los valores posibles para "banderas" están delimitadas, se puede calcular todas sus condicionales como requisitos previos en lugar de envolver columnas en ellos. Esto garantiza un índice de alto rendimiento en cualquiera de las columnas indexadas, y no requiere escribir ningún SQL dinámico. Y es mejor que escribir 4 consultas por separado por razones obvias.

+0

Esto no garantiza una búsqueda de índice de alto rendimiento, ya que volverá a evaluar barFlag> = ISNULL (@barFlag, 0) cada vez. – Russell

+0

@barFlag es solo una variable escalar, no una columna de tabla.Es sargable: puede expandir toda la expresión en 8 banderas de constantes separadas. Pruébalo y mira. – Aaronaught

+0

Disculpas, Aaron, estaba usando la consulta con las expresiones IS NULL O .. :) La evaluación de expresiones numéricas no parece hacer eso. Lo siento – Russell

0
where 
    case when @value<>0 then Field else 1 end 
    = 
    case when @value<>0 then @value else 1 end 
+1

Agregar una explicación textual alrededor de tu código ayudará al asker. – RBT

0
declare @CompanyID as varchar(10) = '' -- or anyother value 

select * from EmployeeChatTbl chat 

    where chat.ConversationDetails like '%'[email protected]+'%' 

       and 
       (
        (0 = CASE WHEN (@CompanyID = '') THEN 0 ELSE 1 END) 
           or 
        (chat.CompanyID = @CompanyID) 
       ) 

trabajo

cuando el CompanyID está presente, entonces la filtración basado en que se hace, de otra forma, la filtración se omite.

Cuestiones relacionadas