2010-03-12 21 views
6

bien aquí está mi problema:parámetros de búsqueda opcionales en la consulta SQL y filas con valores nulos

antes de empezar la descripción, deje que le diga que he buscado en Google un montón y estoy publicar esta pregunta para una buena solución óptima :)

estoy construyendo un servicio de descanso en WCF para obtener UserProfiles ... el usuario puede filtrar UserProfiles dando algo así como UserProfiles? location = Londres

ahora tengo el siguiente método

GetUserProfiles(string firstname, string lastname, string age, string location) 

la cadena de consulta sql que construí es: select firstname, lastname, .... from profiles where (firstName like '%{firstname}%') AND (lastName like '%{lastName}%') .... y así sucesivamente con todas las variables reemplazadas por el formateador de cadenas.

problema con esto es que filtra cualquier fila que tiene nombre, apellido, edad o ubicación que tiene un valor nulo ....

haciendo algo así como (firstName like '%{firstName}%' OR firstName IS NULL) sería tedioso y la declaración se convertiría en unmaintanable! (en este ejemplo, solo hay 4 argumentos, pero en mi método real hay 10)

¿Cuál sería la mejor solución para esto? .... ¿Cómo se maneja esta situación habitualmente?

Base de datos utilizada: MySql

+0

¿Ha considerado usar LINQ-to-SQL? –

+0

no, aún no he considerado linq a sql ... –

Respuesta

1

Puede utilizar COALESCE(value,...)

coalesce(firstName, '') like '%{firstname}%' 
+1

'COALESCE()' es básicamente una búsqueda O (n) ya que no se usarán índices. – cletus

2

Es una propiedad fundamental de NULL que cuando se compara con nada — incluyendo NULL — vuelve false, que es la razón por lo que estás haciendo no funciona.

Así que la primera pregunta que debe responder es: ¿por qué quiere que se devuelva una fila con NULLlastname al ingresar a lastname de "smith"? Posiblemente quiera decir que el primer nombre o el apellido debe coincidir para ser devuelto, en cuyo caso no está ejecutando la consulta correcta. La solución más ingenua es:

SELECT firstname, lastname, .... 
FROM profiles 
WHERE IFNULL(firstName LIKE'%{firstname}%' 
OR lastName LIKE '%{lastName}%' 

Ahora esto va a funcionar durante varios cientos y posiblemente miles de filas, pero no va a escalar más allá de eso por varias razones:

  1. OR s son típicamente muy débil en términos de actuación. Evítalos cuando sea posible. Si nos fijamos en las aplicaciones de bases de datos escritas por programadores experimentados, probablemente no encontrará una sola condición OR (excepto de aquellos que hacen proselitismo las virtudes de OR en la parte posterior de haber escrito una aplicación de libro de visitas que tiene 3 usuarios y 2 visitas al mes) ;
  2. Hacer un LIKE con un % en el frente significará que no se demandarán los índices.

Existen varias soluciones para (2). Probablemente el más fácil en MySQL es usar full text searching en tablas MyISAM. No encontrará coincidencias como "Johannes" si escribe "han", pero en general es suficiente.

A menudo se trata de OR condiciones usando UNION o (preferiblemente) UNION ALL en múltiples consultas. Por ejemplo:

SELECT firstname, lastname, .... 
FROM profiles 
WHERE firstName LIKE'%{firstname}%' 
AND lastName LIKE '%{lastName}%' 
UNION ALL 
SELECT firstname, lastname, .... 
FROM profiles 
WHERE firstName LIKE'%{firstname}%' 
AND lastname IS NULL 
UNION ALL 
SELECT firstname, lastname, .... 
FROM profiles 
WHERE firstName IS NULL 
AND lastName LIKE '%{lastName}%' 
UNION ALL 
SELECT firstname, lastname, .... 
FROM profiles 
WHERE firstName IS NULL 
AND lastName IS NULL 

se ve grande y feo, pero UNION ALL escalas extremadamente bien (ignorando el % en el inicio de los criterios LIKE). Básicamente está concatenando (en este caso) cuatro consultas. A UNION hará un implícito DISTINCT en las filas de resultados, pero sabemos que no habrá superposición aquí porque estamos variando la comprobación de NULL.

Otra posibilidad es no tratar NULL como algo que desea buscar. Esta es una solución mucho mejor e intuitiva (imho). Si alguien teclea el apellido de "Smith", ¿realmente quieres que aparezcan las filas con el apellido NULL?

¿O desea que aparezcan porque es posible que haya encontrado una coincidencia con el primer nombre? Si es así, quieres una consulta ligeramente diferente. Por ejemplo:

SELECT firstname, lastname, .... 
FROM profiles 
WHERE id IN (
    SELECT id 
    FROM profiles 
    WHERE firstName LIKE'%{firstname}%' 
    UNION ALL 
    SELECT id 
    FROM profiles 
    WHERE lastName LIKE '%{lastName}%' 
) 
+0

¿rendimiento con tantas uniones? –

+0

@glenn: como dije, la variante de las cuatro uniones va a ser en gran parte inútil ** en este caso ** porque los criterios no usarán índices de todos modos, pero si estaba usando índices, entonces esa es una historia diferente. El problema más importante es si está ejecutando la consulta correcta y si su modelo de datos no es óptimo o no. – cletus

0

Puede utilizar COALESCE:

select firstname, lastname, .... 
from profiles 
where (coalesce(firstName, '') like '%{firstname}%') 
AND (coalesce(lastName, '') like '%{lastName}%') 
+1

'COALESCE()' es básicamente una búsqueda O (n) ya que no se usarán índices. – cletus

+1

Sí, obviamente, pero no se pueden usar índices de todos modos debido al 'LIKE '% foo%'', así que parece ser un punto discutible aquí. El índice se puede usar con 'LIKE' foo% 'pero no si hay un comodín al inicio. Si el rendimiento es un problema aquí, se debe usar un índice de texto completo. –

0

Siempre podría no permitir valores nulos en el primer lugar - después de todo, todo el mundo debe estar vivo durante algunos años positivos y estar geográficamente en algún lugar.

Si absolutamente debe permitir nulos, entonces use COALESCE, como han sugerido otros, o posiblemente IFNULL que es específico de MySQL y puede ser un poco más óptimo ya que toma exactamente 2 parámetros.

Cuestiones relacionadas