433


139

LINQでのLEFT OUTER JOIN

`join-on-equals-into`句を使用せずにC#LINQでオブジェクトに左外部結合を実行する方法は? 「where」句でそれを行う方法はありますか? 正しい問題:内部結合は簡単で、このような解決策があります

List innerFinal = (from l in lefts from r in rights where l.Key == r.Key
                             select new JoinPair { LeftId = l.Id, RightId = r.Id})

しかし、左外部結合には解決策が必要です。 私のものはこのようなものですが、機能していません

List< JoinPair> leftFinal = (from l in lefts from r in rights
                             select new JoinPair {
                                            LeftId = l.Id,
                                            RightId = ((l.Key==r.Key) ? r.Id : 0
                                        })

JoinPairはクラスです。

public class JoinPair { long leftId; long rightId; }

19 Answer


508


上に述べたように:

var q =
    from c in categories
    join p in products on c.Category equals p.Category into ps
    from p in ps.DefaultIfEmpty()
    select new { Category = c, ProductName = p == null ? "(No products)" : p.ProductName };


451


データベース駆動のLINQプロバイダーを使用する場合、非常に読みやすい左外部結合を次のように記述できます。

from maintable in Repo.T_Whatever
from xxx in Repo.T_ANY_TABLE.Where(join condition).DefaultIfEmpty()

`DefaultIfEmpty()`を省略すると、内部結合ができます。

受け入れられた答えを受け取ります:

  from c in categories
    join p in products on c equals p.Category into ps
    from p in ps.DefaultIfEmpty()

この構文は非常に紛らわしく、複数のテーブルを結合したままにする場合の動作は明確ではありません。

注意 +「Repo.whatever.Where(condition).DefaultIfEmpty()のエイリアスから」は、(適切な)データベース最適化プログラムであるouter-apply / left-join-lateralと同じであることに注意してください行ごとの値(別名実際の外部適用)を導入しない限り、左結合に完全に変換できます。 Linq-2-Objectsではこれを行わないでください(Linq-to-Objectsを使用する場合、DBオプティマイザーがないため)。

詳細な例

var query2 = (
    from users in Repo.T_User
    from mappings in Repo.T_User_Group
         .Where(mapping => mapping.USRGRP_USR == users.USR_ID)
         .DefaultIfEmpty() // <== makes join left join
    from groups in Repo.T_Group
         .Where(gruppe => gruppe.GRP_ID == mappings.USRGRP_GRP)
         .DefaultIfEmpty() // <== makes join left join

    // where users.USR_Name.Contains(keyword)
    // || mappings.USRGRP_USR.Equals(666)
    // || mappings.USRGRP_USR == 666
    // || groups.Name.Contains(keyword)

    select new
    {
         UserId = users.USR_ID
        ,UserName = users.USR_User
        ,UserGroupId = groups.ID
        ,GroupName = groups.Name
    }

);


var xy = (query2).ToList();

LINQ 2 SQLと共に使用すると、次の非常に読みやすいSQLクエリにうまく変換されます。

SELECT
     users.USR_ID AS UserId
    ,users.USR_User AS UserName
    ,groups.ID AS UserGroupId
    ,groups.Name AS GroupName
FROM T_User AS users

LEFT JOIN T_User_Group AS mappings
   ON mappings.USRGRP_USR = users.USR_ID

LEFT JOIN T_Group AS groups
    ON groups.GRP_ID == mappings.USRGRP_GRP

編集:

より複雑な例については、「https://stackoverflow.com/questions/32372167/convert-sql-server-query-to-linq-query/32372420#32372420[SQL ServerクエリをLinqクエリに変換]」も参照してください。

また、(Linq-2-SQLの代わりに)Linq-2-Objectsで実行している場合は、昔ながらの方法で実行する必要があります(LINQ to SQLはこれを正しく結合して操作を結合しますが、完全スキャンを強制し、インデックス検索を利用しませんが、…​):

    var query2 = (
    from users in Repo.T_Benutzer
    join mappings in Repo.T_Benutzer_Benutzergruppen on mappings.BEBG_BE equals users.BE_ID into tmpMapp
    join groups in Repo.T_Benutzergruppen on groups.ID equals mappings.BEBG_BG into tmpGroups
    from mappings in tmpMapp.DefaultIfEmpty()
    from groups in tmpGroups.DefaultIfEmpty()
    select new
    {
         UserId = users.BE_ID
        ,UserName = users.BE_User
        ,UserGroupId = mappings.BEBG_BG
        ,GroupName = groups.Name
    }

);


104


ラムダ式を使用する

db.Categories
  .GroupJoin(
      db.Products,
      Category => Category.CategoryId,
      Product => Product.CategoryId,
      (x, y) => new { Category = x, Products = y })
  .SelectMany(
      xy => xy.Products.DefaultIfEmpty(),
      (x, y) => new { Category = x.Category, Product = y })
  .Select(s => new
  {
      CategoryName = s.Category.Name,
      ProductName = s.Product.Name
  })


36


このhttp://msdn.microsoft.com/en-us/vcsharp/ee908647.aspx#leftouterjoin[example]をご覧ください。 このクエリは機能するはずです。

var leftFinal = from left in lefts
                join right in rights on left equals right.Left into leftRights
                from leftRight in leftRights.DefaultIfEmpty()
                select new { LeftId = left.Id, RightId = left.Key==leftRight.Key ? leftRight.Id : 0 };


29


拡張メソッドとして:

public static class LinqExt
{
    public static IEnumerable LeftOuterJoin(this IEnumerable left, IEnumerable right, Func leftKey, Func rightKey,
        Func result)
    {
        return left.GroupJoin(right, leftKey, rightKey, (l, r) => new { l, r })
             .SelectMany(
                 o => o.r.DefaultIfEmpty(),
                 (l, r) => new { lft= l.l, rght = r })
             .Select(o => result.Invoke(o.lft, o.rght));
    }
}

通常はjoinを使用するように使用します。

var contents = list.LeftOuterJoin(list2,
             l => l.country,
             r => r.name,
            (l, r) => new { count = l.Count(), l.country, l.reason, r.people })

これで時間が節約できることを願います。


16


拡張メソッドによる左外部結合の実装は次のようになります

public static IEnumerable LeftJoin(
  this IEnumerable outer, IEnumerable inner
  , Func outerKeySelector, Func innerKeySelector
  , Func resultSelector, IEqualityComparer comparer)
  {
    if (outer == null)
      throw new ArgumentException("outer");

    if (inner == null)
      throw new ArgumentException("inner");

    if (outerKeySelector == null)
      throw new ArgumentException("outerKeySelector");

    if (innerKeySelector == null)
      throw new ArgumentException("innerKeySelector");

    if (resultSelector == null)
      throw new ArgumentException("resultSelector");

    return LeftJoinImpl(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer ?? EqualityComparer.Default);
  }

  static IEnumerable LeftJoinImpl(
      IEnumerable outer, IEnumerable inner
      , Func outerKeySelector, Func innerKeySelector
      , Func resultSelector, IEqualityComparer comparer)
  {
    var innerLookup = inner.ToLookup(innerKeySelector, comparer);

    foreach (var outerElment in outer)
    {
      var outerKey = outerKeySelector(outerElment);
      var innerElements = innerLookup[outerKey];

      if (innerElements.Any())
        foreach (var innerElement in innerElements)
          yield return resultSelector(outerElment, innerElement);
      else
        yield return resultSelector(outerElment, default(TInner));
     }
   }

その後、resultselectorはnull要素を処理する必要があります。 Fx.

   static void Main(string[] args)
   {
     var inner = new[] { Tuple.Create(1, "1"), Tuple.Create(2, "2"), Tuple.Create(3, "3") };
     var outer = new[] { Tuple.Create(1, "11"), Tuple.Create(2, "22") };

     var res = outer.LeftJoin(inner, item => item.Item1, item => item.Item1, (it1, it2) =>
     new { Key = it1.Item1, V1 = it1.Item2, V2 = it2 != null ? it2.Item2 : default(string) });

     foreach (var item in res)
       Console.WriteLine(string.Format("{0}, {1}, {2}", item.Key, item.V1, item.V2));
   }


9


この例を見てください

class Person
{
    public int ID { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Phone { get; set; }
}

class Pet
{
    public string Name { get; set; }
    public Person Owner { get; set; }
}

public static void LeftOuterJoinExample()
{
    Person magnus = new Person {ID = 1, FirstName = "Magnus", LastName = "Hedlund"};
    Person terry = new Person {ID = 2, FirstName = "Terry", LastName = "Adams"};
    Person charlotte = new Person {ID = 3, FirstName = "Charlotte", LastName = "Weiss"};
    Person arlene = new Person {ID = 4, FirstName = "Arlene", LastName = "Huff"};

    Pet barley = new Pet {Name = "Barley", Owner = terry};
    Pet boots = new Pet {Name = "Boots", Owner = terry};
    Pet whiskers = new Pet {Name = "Whiskers", Owner = charlotte};
    Pet bluemoon = new Pet {Name = "Blue Moon", Owner = terry};
    Pet daisy = new Pet {Name = "Daisy", Owner = magnus};

    // Create two lists.
    List people = new List {magnus, terry, charlotte, arlene};
    List pets = new List {barley, boots, whiskers, bluemoon, daisy};

    var query = from person in people
        where person.ID == 4
        join pet in pets on person equals pet.Owner  into personpets
        from petOrNull in personpets.DefaultIfEmpty()
        select new { Person=person, Pet = petOrNull};



    foreach (var v in query )
    {
        Console.WriteLine("{0,-15}{1}", v.Person.FirstName + ":", (v.Pet == null ? "Does not Exist" : v.Pet.Name));
    }
}

// This code produces the following output:
//
// Magnus:        Daisy
// Terry:         Barley
// Terry:         Boots
// Terry:         Blue Moon
// Charlotte:     Whiskers
// Arlene:

これで、「右に一致する要素がない」場合でも「左からの要素を含める」ことができます。この場合、右に一致しない場合でも「Arlene」を取得しました

ここが参考です

https://msdn.microsoft.com/en-us/library/bb397895.aspx [方法:左外部結合を実行する(C#プログラミングガイド)]


9


これは一般的な形式です(他の回答で既に提供されています)

var c =
    from a in alpha
    join b in beta on b.field1 equals a.field1 into b_temp
    from b_value in b_temp.DefaultIfEmpty()
    select new { Alpha = a, Beta = b_value };

しかし、これが実際に何を意味するのかを明確にしたいという説明があります!

join b in beta on b.field1 equals a.field1 into b_temp

本質的には、右側のエントリ(「b」のエントリ)のヌル「行」を効果的に含む個別の結果セットb_tempを作成します。

次に、次の行:

from b_value in b_temp.DefaultIfEmpty()
.iterates over that result set, setting the default null value for the

右側の「行」、および右側の行結合の結果を「b_value」の値に設定する(つまり、 一致するレコードがある場合は右側にある値、ない場合は「null」)。

これで、右側が個別のLINQクエリの結果である場合、匿名型で構成され、 'something’または 'null’のみになります。 ただし、列挙可能な場合(例: リスト-MyObjectBは2つのフィールドを持つクラスです)、プロパティに使用されるデフォルトの「null」値を特定することができます:

var c =
    from a in alpha
    join b in beta on b.field1 equals a.field1 into b_temp
    from b_value in b_temp.DefaultIfEmpty( new MyObjectB { Field1 = String.Empty, Field2 = (DateTime?) null })
    select new { Alpha = a, Beta_field1 = b_value.Field1, Beta_field2 = b_value.Field2 };

これにより、「b」自体がnullではないことが保証されます(ただし、指定したデフォルトのnull値を使用してそのプロパティをnullにすることができます)。これにより、b_valueのnull参照例外を取得せずにb_valueのプロパティを確認できます。 null許容のDateTimeの場合、(DateTime?)のタイプ、つまり 「nullable DateTime」は、「DefaultIfEmpty」の仕様でnullの「Type」として指定する必要があります(これは、「native」nullableではない型、たとえばdouble、floatにも適用されます)。

上記の構文を単純にチェーンするだけで、複数の左外部結合を実行できます。


7


3つ以上のテーブルを結合する必要がある場合の例を次に示します。

from d in context.dc_tpatient_bookingd
join bookingm in context.dc_tpatient_bookingm
     on d.bookingid equals bookingm.bookingid into bookingmGroup
from m in bookingmGroup.DefaultIfEmpty()
join patient in dc_tpatient
     on m.prid equals patient.prid into patientGroup
from p in patientGroup.DefaultIfEmpty()

参照:https://stackoverflow.com/a/17142392/2343


4


3つのテーブルがあります:person、schools、persons_schools。これは、人を学習先の学校に接続します。 id = 6の人への参照は、persons_schoolsテーブルにありません。 ただし、id = 6の人は、結果のlef-joinedグリッドに表示されます。

List persons = new List
{
    new Person { id = 1, name = "Alex", phone = "4235234" },
    new Person { id = 2, name = "Bob", phone = "0014352" },
    new Person { id = 3, name = "Sam", phone = "1345" },
    new Person { id = 4, name = "Den", phone = "3453452" },
    new Person { id = 5, name = "Alen", phone = "0353012" },
    new Person { id = 6, name = "Simon", phone = "0353012" }
};

List schools = new List
{
    new School { id = 1, name = "Saint. John's school"},
    new School { id = 2, name = "Public School 200"},
    new School { id = 3, name = "Public School 203"}
};

List persons_schools = new List
{
    new PersonSchool{id_person = 1, id_school = 1},
    new PersonSchool{id_person = 2, id_school = 2},
    new PersonSchool{id_person = 3, id_school = 3},
    new PersonSchool{id_person = 4, id_school = 1},
    new PersonSchool{id_person = 5, id_school = 2}
    //a relation to the person with id=6 is absent
};

var query = from person in persons
            join person_school in persons_schools on person.id equals person_school.id_person
            into persons_schools_joined
            from person_school_joined in persons_schools_joined.DefaultIfEmpty()
            from school in schools.Where(var_school => person_school_joined == null ? false : var_school.id == person_school_joined.id_school).DefaultIfEmpty()
            select new { Person = person.name, School = school == null ? String.Empty : school.name };

foreach (var elem in query)
{
    System.Console.WriteLine("{0},{1}", elem.Person, elem.School);
}