2009-08-12 19 views
22

Ho bisogno di fare un join di sinistra su più condizioni in cui le condizioni sono OR s anziché AND s. Ho trovato molti esempi di quest'ultimo, ma sto lottando per ottenere la risposta giusta per il mio scenario.Linq - left join su più condizioni (OR)

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() } 

funziona perfettamente per i join in cui tutte le condizioni devono corrispondere. Ho bisogno di ottenere il join per abbinare on a.col1 = b.col1 OR a.col2 = b.col2.

So che dev'essere facile, ma mi è venuto in mente questo!

Edit:

Per dare un po 'più di informazioni, lo scopo della query è quello di ottenere una proiezione che contiene tutti i campi da 'A' e un conteggio dei record corrispondenti in 'b'. Ho modificato l'esempio sopra per cercare di illustrare ciò che sto cercando. Quando corro con quanto sopra usando l'approccio Jon Skeet ha notato che sto ottenendo un conteggio di tutti i record da un numero, non il conteggio dei record correlati in b.

La base LEFT JOIN funziona bene:

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 } 

Se gli modifico per aggiungere il raggruppamento, come di seguito

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() } 

sto ottenendo il conteggio dei record restituiti da una - non è il conteggio dei record in corrispondenza in b.

Edit:

darò la risposta a Jon - Ho risolto il mio problema di conteggio - non avevo capito che potevo usare una lambda per filtrare il conteggio (g.Count(x => x != null)). Inoltre ho bisogno di raggruppare b da un piuttosto che da un come sopra. Questo dà il risultato corretto ma SQL non è efficiente come lo scriverei a mano in quanto aggiunge una sottoquery correlata - se qualcuno può consigliare un modo migliore di scriverlo per simulare il seguente SQL, lo apprezzerei!

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

Ti suggerisco di provare la mia query * senza * il raggruppamento e vedere se questo sta ottenendo i risultati che ti aspetti. Se lo fa, prova a capire perché il raggruppamento stia fallendo. Magari raggruppa per 'new {a.col1, a.col2}'? –

+0

Funziona bene come selezione di sinistra di base select - ottenere il conteggio del gruppo non si comporta come mi aspetterei se fosse un equi-join di base. Nota le informazioni extra sopra. –

risposta

32

LINQ supporta solo direttamente equijoins. Se si vuole fare qualsiasi altro tipo di join, è fondamentalmente bisogno di un cross-aderire e where:

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

E 'probabilmente la pena di verificare ciò che l'SQL generato come appare e come il piano di query è come. Ci possono essere modi più efficienti per farlo, ma questo è probabilmente l'approccio più semplice.

+0

Ho iniziato con questo approccio ... In realtà sto raggruppando la query appropriata per ottenere tutti i record da ae il conteggio dei record corrispondenti da b. Modificherò il post per chiarire. –

20

seconda del provider di query, si può solo scegliere di utilizzare due da clausole:

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

Il che, se si esegue su un DB, sarà altrettanto efficace. Se esegui in memoria (da Linq a Objects), questo enumererà tutte le combinazioni possibili, che potrebbero essere inefficienti.

Arg, Skeeted ;-).

Sono possibili alternative più efficienti da Linq a Objects. L'operatore join enumera ciascuna sorgente una sola volta, quindi esegue un hash-join, in modo da poter dividere la clausola-or in due join separati e quindi prendere la loro unione.Un'unione in LINQ è solo una concatenazione senza duplicati, in modo che apparirebbe come segue:

(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() 

questo approccio funziona, ed è solo una query, ma è un po 'non ovvio, nel senso che le caratteristiche prestazionali del il codice dipende dalla comprensione dettagliata di come funziona linq. Personalmente, se vuoi fare un hash-join con potenzialmente più corrispondenze, uno strumento più ovvio è ToLookup. Un'alternativa usando che potrebbe apparire come segue:

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() 
    ... 

Questa soluzione è in realtà più corta, e il motivo per cui funziona è più evidente, quindi è quello che preferisco. Ricorda che se dividi l'operatore || in due query separate come nei due scenari precedenti, devi evitare manualmente il conteggio doppio dei risultati (ad esempio, utilizza Distinct).

+0

Sto usando Linq agli oggetti - esiste una soluzione alternativa? – Kev

+0

Certo, vedi modifica! –

+0

Cura di unirsi a me? : http://chat.stackoverflow.com/rooms/info/17239/kryptonite – Kev