2009-08-12 8 views
22

Je dois effectuer une jointure à gauche dans plusieurs conditions où les conditions sont OR s plutôt que AND s. J'ai trouvé beaucoup d'échantillons de ce dernier mais j'ai du mal à trouver la bonne réponse pour mon scénario.Linq - jointure à gauche sur plusieurs conditions (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() } 

fonctionne très bien pour les jointures où toutes les conditions doivent correspondre. Je dois obtenir la jointure pour correspondre à on a.col1 = b.col1 OR a.col2 = b.col2.

Je sais que ça doit être facile mais je n'ai rien trouvé sur ce sujet!

Edit:

Pour donner un peu plus d'information, dans le but de la requête est d'obtenir une projection contenant tous les champs de « a » plus un décompte des enregistrements correspondants dans « b ». J'ai modifié l'exemple ci-dessus pour essayer d'illustrer ce que je cherche. Quand je cours avec ce qui précède en utilisant l'approche Jon Skeet a noté que je reçois un compte de tous les enregistrements d'un, pas le nombre d'enregistrements liés en b.

La gauche base join ne fonctionne 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 je révise pour ajouter le regroupement comme ci-dessous

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

Je reçois le nombre des enregistrements renvoyés d'un - pas nombre d'enregistrements en correspondance dans b.

Edit:

Je vais vous donner la réponse à Jon - J'ai résolu mon problème de comptage - Je ne savais pas que je pouvais utiliser un lamda pour filtrer le nombre (g.Count(x => x != null)). De plus, j'ai besoin de grouper b par un plutôt que par un comme je l'ai dit ci-dessus. Cela donne le résultat correct mais le SQL n'est pas aussi efficace que je l'écrirais à la main car il ajoute une sous-requête corrélée - si quelqu'un peut conseiller une meilleure façon de l'écrire pour simuler le SQL suivant je l'apprécierais!

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

Je vous suggère d'essayer ma requête * sans * le regroupement, et de voir si cela obtient les résultats que vous attendez. Si c'est le cas, essayez de déterminer pourquoi le regroupement échoue. Peut-être grouper par 'new {a.col1, a.col2}'? –

+0

Fonctionne très bien en tant que jointure de base à gauche - le fait d'obtenir le compte en dehors du regroupement ne se comporte pas comme je le souhaiterais s'il s'agissait d'une équi-jointure de base. Notez les informations supplémentaires ci-dessus. –

Répondre

32

LINQ prend uniquement en charge les équijoins. Si vous voulez faire un autre type de jointure, vous avez besoin essentiellement une croix affilient et where:

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

Il est probablement utile de vérifier ce que le SQL généré ressemble et ce que le plan de requête est. Il peut y avoir des façons plus efficaces de le faire, mais c'est probablement l'approche la plus simple.

+0

J'ai commencé avec cette approche ... Je suis en train de regrouper dans la requête proprement dite pour obtenir tous les enregistrements de a et le nombre d'enregistrements correspondants de b. Je vais modifier le post pour clarifier. –

20

Selon le fournisseur de requête, vous pouvez simplement choisir d'utiliser deux des clauses:

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

qui, si vous exécutez sur un DB, sera tout aussi efficace. Si vous exécutez en-mémoire (Linq to Objects), cela énumérera toutes les combinaisons possibles, ce qui peut être inefficace.

Arg, Skeeted ;-).

Des alternatives Linq to Objects plus efficaces sont possibles. L'opérateur join énumère chaque source une seule fois, puis effectue une jointure de hachage, de sorte que vous pouvez diviser la clause-or en deux jointures séparées, puis prendre leur union.Une union dans LINQ est juste une concaténation sans doublons, de sorte que regarderait comme suit:

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

Cette approche fonctionne, et il est juste une question, mais il est un peu non évidente en ce sens que les caractéristiques de performance du Le code dépend de la compréhension détaillée du fonctionnement de linq. Personnellement, si vous souhaitez effectuer une jointure par hachage avec des correspondances potentiellement multiples, un outil plus évident est ToLookup. Une alternative à l'aide qui pourrait se présenter comme suit:

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

Cette solution est en fait plus court, et la raison pour laquelle il travaille est plus évidente, il est celui que je préfère. N'oubliez pas que si vous divisez l'opérateur || en deux requêtes distinctes, comme dans les deux scénarios ci-dessus, vous devez éviter manuellement de compter deux fois les résultats (c'est-à-dire utiliser Distinct).

+0

J'utilise Linq pour les objets - existe-t-il une solution alternative? – Kev

+0

Bien sûr, voir modifier! –

+0

Vous voulez vous joindre à moi? : http://chat.stackoverflow.com/rooms/info/17239/kryptonite – Kev

Questions connexes