次の方法で共有


join 句 (C# リファレンス)

更新 : 2007 年 11 月

オブジェクト モデル内で直接的な関係を持たない、異なるソース シーケンスの要素どうしを関連付けるには、join 句を使用すると便利です。唯一の要件は、各ソースの要素が、等価比較できるような値を共有していることです。たとえば、食品販売業者が、特定の商品についての仕入先一覧とバイヤー一覧を持っているとします。join 句を使用すると、その商品に関して、指定した同じ地域の仕入先とバイヤーの一覧を作成できます。

join 句は、入力として 2 つのソース シーケンスを受け取ります。各シーケンス内の要素は、対応するもう一方のシーケンスのプロパティと比較できるプロパティであるか、このプロパティを含んでいる必要があります。join 句は、特別な equals キーワードを使用して、指定したキーが等しいかどうかを比較します。join 句によって実行される結合は、すべて等結合です。join 句の出力結果は、実行する結合の種類によって異なります。最も一般的な結合の種類は、次の 3 つです。

  • 内部結合

  • グループ化結合

  • 左外部結合

内部結合

簡単な内部等結合の例を次に示します。このクエリでは、"商品名/カテゴリ" ペアのフラットなシーケンスが生成されます。同じカテゴリ文字列が、複数の要素に表示されます。categories の要素に一致する要素が products 内にない場合、そのカテゴリは結果に表示されません。

var innerJoinQuery =
    from category in categories
    join prod in products on category.ID equals prod.CategoryID
    select new { ProductName = prod.Name, Category = category.Name }; //produces flat sequence

詳細については、「方法 : 内部結合を実行する (C# プログラミング ガイド)」を参照してください。

グループ化結合

into 式と組み合わせた join 句は、グループ化結合と呼ばれます。

var innerGroupJoinQuery =
    from category in categories
    join prod in products on category.ID equals prod.CategoryID into prodGroup
    select new { CategoryName = category.Name, Products = prodGroup };

グループ化結合では、階層構造の結果シーケンスが生成され、左側のソース シーケンスの要素が、右側のソース シーケンスに含まれる 1 つ以上の一致する要素に関連付けられます。グループ化結合は基本的にオブジェクト配列のシーケンスであり、リレーショナル データベースの用語には、これに相当するものがありません。

右側のソース シーケンスに左側のソースの要素と一致する要素がない場合、join 句はその項目について空の配列を生成します。このため、結果のシーケンスがグループ別に編成される点を除き、グループ化結合は基本的に内部等結合です。

グループ化結合の結果を単に選択した場合、項目にアクセスすることはできますが、一致の基準となるキーを特定することはできません。したがって、一般に、前の例に示すように、グループ化結合の結果を選択し、キー名を持つ新しい型に格納した方が便利です。

グループ化結合の結果を、別のサブクエリのジェネレータとして使用することもできます。

var innerGroupJoinQuery2 =
    from category in categories
    join prod in products on category.ID equals prod.CategoryID into prodGroup
    from prod2 in prodGroup
        where prod2.UnitPrice > 2.50M
        select prod2;    

詳細については、「方法 : グループ化結合を実行する (C# プログラミング ガイド)」を参照してください。

左外部結合

左外部結合では、右側のシーケンスに一致する要素がない場合でも、左側のソース シーケンスに含まれるすべての要素が返されます。LINQ で左外部結合を実行するには、DefaultIfEmpty メソッドをグループ化結合と組み合わせて使用して、左側の要素に一致する要素がなかった場合に生成する、既定の右側の要素を指定します。null は、任意の参照型の既定値として使用できます。また、ユーザー定義の既定の型を指定することもできます。次の例では、ユーザー定義の既定の型を示しています。

var leftOuterJoinQuery =
    from category in categories
    join prod in products on category.ID equals prod.CategoryID into prodGroup
    from item in prodGroup.DefaultIfEmpty(new Product{Name = String.Empty, CategoryID = 0})
        select new { CatName = category.Name, ProdName = item.Name };

詳細については、「方法 : 左外部結合を実行する (C# プログラミング ガイド)」を参照してください。

等値演算子

join 句は、等結合を実行します。つまり、2 つのキーが等しい場合のみ、一致が結合されます。他の種類の比較 ("より大きい"、"等しくない" など) はサポートされません。すべての結合が等結合であることを明確にするため、join 句では、== 演算子の代わりに equals キーワードが使用されます。join 句では equals キーワードのみ使用できます。このキーワードと == 演算子には、重要な違いが 1 つあります。equals の場合、左辺のキーは外部のソース シーケンスを使用し、右辺のキーは内部のソースを使用します。外部のソースは equals の左辺のスコープにしかなく、内部のソース シーケンスは右辺のスコープにしかありません。

非等結合

複数の from 句を使用して新しいシーケンスをクエリに個別に導入すると、非等結合、クロス結合、および他のカスタム結合操作を実行できます。詳細については、「方法 : カスタム結合操作を実行する (C# プログラミング ガイド)」を参照してください。

オブジェクト コレクションの結合とリレーショナル テーブルの結合

LINQ クエリ式では、結合操作はオブジェクト コレクションに対して実行されます。オブジェクト コレクションは、2 つのリレーショナル テーブルを結合するのとまったく同じ方法で "結合" することはできません。LINQ では、2 つのソース シーケンスの間に何の関連付けもない場合のみ、明示的な join 句が必要です。LINQ to SQL を使用する場合、オブジェクト モデルの外部キー テーブルは、主テーブルのプロパティとして表されます。たとえば、Northwind データベースの Customer テーブルと Orders テーブルとの間には、外部キー リレーションシップがあります。この 2 つのテーブルをオブジェクト モデルにマップすると、Customer クラスは Orders プロパティを持っていて、Orders プロパティにはその Customer に関連付けられた Orders のコレクションが格納されていることになります。つまり、結合は既に自動的に行われています。

LINQ to SQL のコンテキストで複数の関連テーブルを照会する方法の詳細については、「方法 : データベース リレーションシップを割り当てる (LINQ to SQL)」を参照してください。

複合キー

複合キーを使用すると、複数の値が等しいかどうかをテストできます。詳細については、「方法 : 複合キーを使用して結合する (C# プログラミング ガイド)」を参照してください。複合キーは、group 句でも使用できます。

使用例

同じデータ ソースに対して同じ一致するキーを使用して内部結合、グループ化結合、および左外部結合を実行した結果の比較を次の例に示します。これらの例には、コンソールでの表示結果をわかりやすくするために、余分なコードが追加されています。

    class JoinDemonstration
    {
        #region Data

        class Product
        {
            public string Name { get; set; }
            public int CategoryID { get; set; }
        }

        class Category
        {
            public string Name { get; set; }
            public int ID { get; set; }
        }

        // Specify the first data source.
        List<Category> categories = new List<Category>()
        { 
            new Category(){Name="Beverages", ID=001},
            new Category(){ Name="Condiments", ID=002},
            new Category(){ Name="Vegetables", ID=003},
            new Category() {  Name="Grains", ID=004},
            new Category() {  Name="Fruit", ID=005}            
        };

        // Specify the second data source.
        List<Product> products = new List<Product>()
       {
          new Product{Name="Cola",  CategoryID=001},
          new Product{Name="Tea",  CategoryID=001},
          new Product{Name="Mustard", CategoryID=002},
          new Product{Name="Pickles", CategoryID=002},
          new Product{Name="Carrots", CategoryID=003},
          new Product{Name="Bok Choy", CategoryID=003},
          new Product{Name="Peaches", CategoryID=005},
          new Product{Name="Melons", CategoryID=005},
        };
        #endregion


        static void Main(string[] args)
        {
            JoinDemonstration app = new JoinDemonstration();

            app.InnerJoin();
            app.GroupJoin();
            app.GroupInnerJoin();
            app.GroupJoin3();
            app.LeftOuterJoin();
            app.LeftOuterJoin2();

            // Keep the console window open in debug mode.
            Console.WriteLine("Press any key to exit.");
            Console.ReadKey();
        }

        void InnerJoin()
        {
            // Create the query that selects 
            // a property from each element.
            var innerJoinQuery =
               from category in categories
               join prod in products on category.ID equals prod.CategoryID
               select new { Category = category.ID, Product = prod.Name };

            Console.WriteLine("InnerJoin:");
            // Execute the query. Access results 
            // with a simple foreach statement.
            foreach (var item in innerJoinQuery)
            {
                Console.WriteLine("{0,-10}{1}", item.Product, item.Category);
            }
            Console.WriteLine("InnerJoin: {0} items in 1 group.", innerJoinQuery.Count());
            Console.WriteLine(System.Environment.NewLine);

        }

        void GroupJoin()
        {
            // This is a demonstration query to show the output
            // of a "raw" group join. A more typical group join
            // is shown in the GroupInnerJoin method.
            var groupJoinQuery =
               from category in categories
               join prod in products on category.ID equals prod.CategoryID into prodGroup
               select prodGroup;

            // Store the count of total items (for demonstration only).
            int totalItems = 0;

            Console.WriteLine("Simple GroupJoin:");

            // A nested foreach statement is required to access group items.
            foreach (var prodGrouping in groupJoinQuery)
            {
                Console.WriteLine("Group:");
                foreach (var item in prodGrouping)
                {
                    totalItems++;
                    Console.WriteLine("   {0,-10}{1}", item.Name, item.CategoryID);
                }
            }
            Console.WriteLine("Unshaped GroupJoin: {0} items in {1} unnamed groups", totalItems, groupJoinQuery.Count());
            Console.WriteLine(System.Environment.NewLine);
        }

        void GroupInnerJoin()
        {
            var groupJoinQuery2 =
                from category in categories
                orderby category.ID
                join prod in products on category.ID equals prod.CategoryID into prodGroup
                select new
                {
                    Category = category.Name,
                    Products = from prod2 in prodGroup
                               orderby prod2.Name
                               select prod2
                };

            //Console.WriteLine("GroupInnerJoin:");
            int totalItems = 0;

            Console.WriteLine("GroupInnerJoin:");
            foreach (var productGroup in groupJoinQuery2)
            {
                Console.WriteLine(productGroup.Category);
                foreach (var prodItem in productGroup.Products)
                {
                    totalItems++;
                    Console.WriteLine("  {0,-10} {1}", prodItem.Name, prodItem.CategoryID);
                }
            }
            Console.WriteLine("GroupInnerJoin: {0} items in {1} named groups", totalItems, groupJoinQuery2.Count());
            Console.WriteLine(System.Environment.NewLine);
        }

        void GroupJoin3()
        {

            var groupJoinQuery3 =
                from category in categories
                join product in products on category.ID equals product.CategoryID into prodGroup
                from prod in prodGroup
                orderby prod.CategoryID
                select new { Category = prod.CategoryID, ProductName = prod.Name };

            //Console.WriteLine("GroupInnerJoin:");
            int totalItems = 0;

            Console.WriteLine("GroupJoin3:");
            foreach (var item in groupJoinQuery3)
            {
                totalItems++;
                Console.WriteLine("   {0}:{1}", item.ProductName, item.Category);
            }

            Console.WriteLine("GroupJoin3: {0} items in 1 group", totalItems, groupJoinQuery3.Count());
            Console.WriteLine(System.Environment.NewLine);
        }

        void LeftOuterJoin()
        {
            // Create the query.
            var leftOuterQuery =
               from category in categories
               join prod in products on category.ID equals prod.CategoryID into prodGroup
               select prodGroup.DefaultIfEmpty(new Product() { Name = "Nothing!", CategoryID = category.ID });

            // Store the count of total items (for demonstration only).
            int totalItems = 0;

            Console.WriteLine("Left Outer Join:");

            // A nested foreach statement  is required to access group items
            foreach (var prodGrouping in leftOuterQuery)
            {
                Console.WriteLine("Group:", prodGrouping.Count());
                foreach (var item in prodGrouping)
                {
                    totalItems++;
                    Console.WriteLine("  {0,-10}{1}", item.Name, item.CategoryID);
                }
            }
            Console.WriteLine("LeftOuterJoin: {0} items in {1} groups", totalItems, leftOuterQuery.Count());
            Console.WriteLine(System.Environment.NewLine);
        }

        void LeftOuterJoin2()
        {
            // Create the query.
            var leftOuterQuery2 =
               from category in categories
               join prod in products on category.ID equals prod.CategoryID into prodGroup
               from item in prodGroup.DefaultIfEmpty()
               select new { Name = item == null ? "Nothing!" : item.Name, CategoryID = category.ID };

            Console.WriteLine("LeftOuterJoin2: {0} items in 1 group", leftOuterQuery2.Count());
            // Store the count of total items
            int totalItems = 0;

            Console.WriteLine("Left Outer Join 2:");

            // Groups have been flattened.
            foreach (var item in leftOuterQuery2)
            {
                totalItems++;
                Console.WriteLine("{0,-10}{1}", item.Name, item.CategoryID);
            }
            Console.WriteLine("LeftOuterJoin2: {0} items in 1 group", totalItems);



/*Output:

InnerJoin:
Cola      1
Tea       1
Mustard   2
Pickles   2
Carrots   3
Bok Choy  3
Peaches   5
Melons    5
InnerJoin: 8 items in 1 group.


Unshaped GroupJoin:
Group:
   Cola      1
   Tea       1
Group:
   Mustard   2
   Pickles   2
Group:
   Carrots   3
   Bok Choy  3
Group:
Group:
   Peaches   5
   Melons    5
Unshaped GroupJoin: 8 items in 5 unnamed groups


GroupInnerJoin:
Beverages
  Cola       1
  Tea        1
Condiments
  Mustard    2
  Pickles    2
Vegetables
  Bok Choy   3
  Carrots    3
Grains
Fruit
  Melons     5
  Peaches    5
GroupInnerJoin: 8 items in 5 named groups


GroupJoin3:
   Cola:1
   Tea:1
   Mustard:2
   Pickles:2
   Carrots:3
   Bok Choy:3
   Peaches:5
   Melons:5
GroupJoin3: 8 items in 1 group


Left Outer Join:
Group:
  Cola      1
  Tea       1
Group:
  Mustard   2
  Pickles   2
Group:
  Carrots   3
  Bok Choy  3
Group:
  Nothing!  4
Group:
  Peaches   5
  Melons    5
LeftOuterJoin: 9 items in 5 groups


LeftOuterJoin2: 9 items in 1 group
Left Outer Join 2:
Cola      1
Tea       1
Mustard   2
Pickles   2
Carrots   3
Bok Choy  3
Nothing!  4
Peaches   5
Melons    5
LeftOuterJoin2: 9 items in 1 group
Press any key to exit.
*/

解説

join 句の後に into がない場合は、Join メソッドの呼び出しに変換されます。join 句の後に into がある場合は、GroupJoin メソッドの呼び出しに変換されます。

参照

処理手順

方法 : 左外部結合を実行する (C# プログラミング ガイド)

方法 : 内部結合を実行する (C# プログラミング ガイド)

方法 : グループ化結合を実行する (C# プログラミング ガイド)

方法 : join 句の結果の順序を指定する (C# プログラミング ガイド)

方法 : 複合キーを使用して結合する (C# プログラミング ガイド)

概念

LINQ クエリ式 (C# プログラミング ガイド)

結合演算

参照

group 句 (C# リファレンス)

その他の技術情報

クエリ キーワード (C# リファレンス)