2009-08-12 17 views
22

Necesito hacer una combinación izquierda en múltiples condiciones donde las condiciones son OR s en lugar de AND s. He encontrado muchas muestras de este último pero estoy luchando por obtener la respuesta correcta para mi situación.Linq - combinación izquierda en múltiples (O) condiciones

from a in tablea 
join b in tableb on new { a.col1, a.col2 } equals new { b.col1, b.col2 } 
group a by a into g 
select new() { col1 = a.col1, col2 = a.col2, count = g.Count() } 

funciona muy bien para uniones donde todas las condiciones deben coincidir. Necesito obtener la unión para que coincida con on a.col1 = b.col1 OR a.col2 = b.col2.

Sé que debe ser fácil, pero me he quedado en blanco en esto!

Editar:

para dar un poco más de información, con el propósito de la consulta es conseguir una proyección que contiene todos los campos de la 'a' más un recuento de los registros coincidentes en 'b'. Modifiqué el ejemplo anterior para intentar ilustrar lo que busco. Cuando corro con lo anterior usando el enfoque que Jon Skeet ha notado obtengo un recuento de todos los registros de a, no el conteo de los registros relacionados en b.

La izquierda se unen básica funciona bien:

from a in tablea 
from b in tableb 
.Where(b => (a.col1 == b.col1 || a.col2 == b.col2)) 
.DefaultIfEmpty() 
select new { col1 = a.col1, col2 = a.col2 } 

Si revisarlo para agregar la agrupación de la siguiente manera

from a in tablea 
from b in tableb 
.Where(b => (a.col1 == b.col1 || a.col2 == b.col2)) 
.DefaultIfEmpty() 
group a by a.col1 into g 
select new { col1 = g.Key, count = g.Count() } 

estoy recibiendo el conteo de los registros devueltos desde una - no es el recuento de registros en concordancia en b.

Editar:

voy a dar la respuesta a Jon - he resuelto mi problema recuento - que no se había dado cuenta que podría utilizar un lambda para filtrar el recuento (g.Count(x => x != null)). Además, necesito agrupar b por a en lugar de por a como lo tenía anteriormente. Esto da el resultado correcto, pero el SQL no es tan eficiente como lo escribiría a mano, ya que agrega una sub consulta correlacionada. Si alguien puede aconsejar una mejor forma de escribirlo para simular el siguiente SQL, ¡lo agradecería!

select a.col1, count(b.col1) 
from tablea a 
left join tableb b 
on a.col1 = b.col1 
or a.col2 = b.col2 
group by a.col1 
+0

Le sugiero que intente mi consulta * sin * la agrupación, y vea si eso está obteniendo los resultados que espera. Si lo hace, intente averiguar por qué la agrupación está fallando. ¿Tal vez agrupar por 'nuevo {a.col1, a.col2}'? –

+0

Funciona bien como una selección básica de combinación a la izquierda: obtener el conteo fuera de la agrupación no se está comportando como esperaba si fuera una combinación equitativa básica. Tenga en cuenta la información adicional anterior. –

Respuesta

32

LINQ solo admite directamente equijoins. Si desea hacer cualquier otro tipo de combinación, que básicamente necesita una cruz-unes y where:

from a in tablea 
from b in tableb 
where a.col1 == b.col1 || a.col2 == b.col2 
select ... 

Es probablemente vale la pena mirar lo que el SQL generado se parece y lo que el plan de consulta es similar. Puede haber formas más eficientes de hacerlo, pero este es probablemente el el más simple.

+0

Empecé con este enfoque ... En realidad, estoy agrupando en la consulta adecuada para obtener todos los registros de a y el recuento de registros coincidentes de b. Editaré la publicación para aclararla. –

20

Dependiendo del proveedor de consulta, usted podría elegir utilizar dos de las cláusulas:

from a in tablea 
from b in tableb 
where a.col1 == b.col1 || a.col2 == b.col2 

Lo cual, si se ejecuta en una base de datos, será igual de eficaz. Si ejecuta en memoria (Linq to Objects), esto enumerará todas las combinaciones posibles, que pueden ser ineficientes.

Arg, Skeeted ;-).

Opciones más eficaces de Linq to Objects son posibles. El operador join enumera cada origen solo una vez, y luego realiza un hash-join, por lo que puede dividir la cláusula-or en dos uniones separadas, y luego tomar su unión.Una unión en LINQ es simplemente una concatenación sin duplicados, por lo que se vería como sigue:

(from a in tablea 
join b in tableb on a.Col1 equals b.Col1 
select new {a, b}) 
.Concat(
from a in tablea 
join b in tableb on a.Col2 equals b.Col2 
select new {a, b} 
).Distinct() 

Este enfoque funciona, y es sólo una consulta, pero es algo no evidente en el sentido de que las características de funcionamiento de la el código depende de entender en detalle cómo funciona linq. Personalmente, si quiere hacer un hash-join con potencialmente múltiples coincidencias, una herramienta más obvia es ToLookup. Una alternativa que podría usar el siguiente aspecto:

var bBy1 = tableb.ToLookup(b=>b.Col1); 
var bBy2 = tableb.ToLookup(b=>b.Col2); 
var q3 = 
    from a in tablea 
    from b in bBy1[a.Col1].Concat(bBy2[a.Col2]).Distinct() 
    ... 

Esta solución es en realidad más corto, y la razón por la que funciona es más evidente, así que es la que yo prefiero. Solo recuerde que si divide el operador || en dos consultas separadas, como en los dos escenarios anteriores, debe evitar manualmente contar dos veces los resultados (es decir, use Distinct).

+0

Estoy utilizando Linq para objetos: ¿hay una solución alternativa? – Kev

+0

Claro, ver la edición! –

+0

¿Quieres unirte a mí? : http://chat.stackoverflow.com/rooms/info/17239/kryptonite – Kev

Cuestiones relacionadas