2010-11-17 15 views
5

Tengo una aplicación que permite buscar una determinada entidad en función de varios criterios diferentes (en algún lugar del orden de 20 métodos diferentes en total). Quiero poder combinar los resultados de varias búsquedas para producir un solo conjunto de resultados.Estrategia general para búsquedas complejas en varias etapas

Por ejemplo:

results = (entities from search 1 AND entities from search 2) OR (entities from search 3) 

Vamos a suponer que las búsquedas son lo suficientemente complejas en la naturaleza, que combina en una sola consulta lógica no es posible (debido a las relaciones complejas que necesitan ser consultada, etc.)

Supongamos también que el número de entidades involucradas (probable) hace que cualquier tipo de estrategia en memoria sea inviable.

Mis pensamientos iniciales eran algo a lo largo de las líneas de:

1) Llevar a cabo las búsquedas por separado, obtener una lista de resultados con "ID de entidad" de cada uno de ellos, y luego realizar un "nivel de raíz" búsqueda basados sobre estos.

Por ejemplo:

select * from entity e 
where 
(e.Id in (search 1 id list) AND e.Id in(search 2 id list)) 
OR e.Id in (search 3 id list) 

2) realizar una consulta externa que selecciona la entidad en base a los resultados devueltos por mis subconsultas (complejo).

Por ejemplo:

select * from entity e 
where (e.Id in (select e1.id from entity e1 where ...) AND e.Id in (select e2.id from entity e2 where...)) 
OR e.Id in (select e3.id from entity e3 where...) 

Obviamente estos ejemplos se han simplificado de manera drástica para fines de ilustración; las consultas individuales serán mucho más complicadas, y la combinación de ellas será arbitraria (acabo de ilustrar un ejemplo representativo aquí).

Estaría muy interesado en escuchar sugerencias sobre cómo otros han manejado esta situación. Estoy ciertamente abierto a cualquier posibilidad que no haya explorado anteriormente.

Como referencia, esta es una aplicación .NET que hace uso de un ORM NHibernate respaldado por una base de datos SQL Server 2008 R2.

Ya he decidido usar hql o sql nativo para esto ya que ICriteria o Linq no proporcionan la flexibilidad necesaria para realizar las consultas individuales ni las operaciones de combinación requeridas.

Respuesta

2

Lo he hecho manteniendo los contadores de rendimiento de búsqueda en una tabla. Básicamente, se monitorea el porcentaje promedio de filas que filtra la búsqueda y el tiempo de ejecución.

entonces crear una cifra de rendimiento basado en TotalNumberOfRowsToSearch * Percent_Not_Matched/RunTimeInSeconds Esta figura es una correlación directa de filas por segundo se puede filtrar. Promediado en miles de carreras, es una predicción bastante buena.

A continuación, ejecuto cada consulta en orden con el rendimiento más alto primero la figura uno.

Si realiza un Y lógico en el resultado total, ejecute cada consulta posterior solo sobre los resultados de la consulta anterior.

Si está haciendo un OR lógico, ejecute cada consulta siguiente solo en los resultados NO EN los resultados de búsqueda anteriores combinados.

Al hacerlo de esta manera, su consulta cambiará en función de los índices y tipos de datos.

Si desea una solución menos dinámica, simplemente calcule las cifras de rendimiento para cada parte de la búsqueda y utilice primero las de mejor rendimiento. Recuerde que una consulta que se ejecuta en 55 ms pero coincide con el 99% de los resultados no es tan útil como una que se ejecuta en 1 segundo y coincide con el 1% de los resultados, así que tenga cuidado de que los resultados puedan ir en contra de sus ideas iniciales.

Solo tenga en cuenta el error de división por 0 al calcular las cifras de rendimiento.

+0

Gracias por sonar, esto es muy informativo desde un aspecto de rendimiento (que es una consideración importante, obviamente). – DanP

0

Si puede usar ICriteria, lo recomendaría. Puede reducir drásticamente la cantidad de código con búsquedas complejas. Por ejemplo, la diferencia entre usar una búsqueda por sí mismo y usarla como subconsulta en la búsqueda agregada sería una proyección adicional.

No he intentado hasta ahora dividir búsquedas complejas y ejecutarlas por separado. Combinar toda la búsqueda en una sola llamada a la base de datos, según su segundo ejemplo, hasta ahora me ha funcionado. Si no obtengo un tiempo de respuesta decente (minutos en lugar de segundos), el Asesor de ajuste de motor de la base de datos ha demostrado ser muy valioso con los índices y estadísticas sugeridos.

+0

En general, estoy totalmente de acuerdo con esto ... desafortunadamente necesito realizar algo de magia sql debido a alguna herencia compleja utilizada en el modelo, por lo que es beneficioso usar sql en algunos lugares (y probablemente sea más eficiente ya que puedo evitar combinaciones innecesarias, etc.) – DanP

2

Mi enfoque utilizando Linq es mediante la construcción de una lista de expresiones de donde se construyen los criterios complejos, y aplicarlos juntos al final.

Algo así:

List<Expression<Func<WorkItem, bool>>> whereExpressions = new List<Expression<Func<WorkItem, bool>>>(); 
if (!string.IsNullOrEmpty(searchMask)) 
      { 
       whereExpressions.Add(
             x => 
             (x.Name.ToLower().IndexOf(searchMask.ToLower()) > -1 || 
             x.Id.ToString().IndexOf(searchMask) > -1 || 
             (x.Description != null && 
              x.Description.ToLower().IndexOf(searchMask.ToLower()) > -1))); 
      } 

whereExpressions.Add(x => (x.Status == status)); 

Finalmente después de la construcción de la lista de expresiones que se aplican las expresiones:

IQueryable<WorkItem> result = Session.Linq<WorkItem>(); 
foreach (Expression<Func<WorkItem, bool>> whereExpression in whereExpressions) 
      { 
       result = result.Where(whereExpression); 
      } 

También puede proporcionar flexibilidad en el método de clasificación y permitir paginación:

IQueryable<WorkItem> items; 
      if (ascOrDesc == "asc") 
      { 
       items = result.OrderBy(DecideSelector(indexer)).Skip(startPoint - 1).Take(numOfrows); 
      } 
      else 
      { 
       items = result.OrderByDescending(DecideSelector(indexer)).Skip(startPoint - 1).Take(numOfrows); 
      } 

Donde DecideSelector se define así:

private Expression<Func<WorkItem, object>> DecideSelector(string fieldCode) 
     { 
      switch (fieldCode) 
      { 
       case "Deadline": 
        return item => item.Deadline; 
       case "name": 
        return item => item.Name; 
       case "WiStatus": 
        return item => item.Status; 
       case "WiAssignTo": 
        return item => item.AssignedUser; 
       default: 
        return item => item.Id; 
      } 
     } 
+0

Esto es lo que suelo hacer también; Desafortunadamente las capacidades de linq de nhibernate no son suficientes para mis necesidades (cuando digo búsquedas "complicadas", lo digo en serio;)) – DanP

Cuestiones relacionadas