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

join 句は、オブジェクト モデル内での直接リレーションシップがない、さまざまなソース シーケンスの要素を関連付ける際に役立ちます。The join clause is useful for associating elements from different source sequences that have no direct relationship in the object model. 唯一の要件は、等価性を比較できるいくつかの値が各ソース内の要素間で共有されていることです。The only requirement is that the elements in each source share some value that can be compared for equality. たとえば、食品販売会社には特定の商品についての供給元のリストと購入者のリストがあります。For example, a food distributor might have a list of suppliers of a certain product, and a list of buyers. join 句は、たとえば指定された地域のすべての供給元および購入者のリストを作成するために使用できます。A join clause can be used, for example, to create a list of the suppliers and buyers of that product who are all in the same specified region.

join 句では、2 つのソース シーケンスを入力として受け取ります。A join clause takes two source sequences as input. 各シーケンスの要素は、もう一方のシーケンスの対応するプロパティと比較できるプロパティであるか、そのプロパティを含む要素であることが必要です。The elements in each sequence must either be or contain a property that can be compared to a corresponding property in the other sequence. join 句では、特殊な equals キーワードを使用して、指定されたキーの等価性が比較されます。The join clause compares the specified keys for equality by using the special equals keyword. join 句によって実行される結合はすべてが等結合です。All joins performed by the join clause are equijoins. join 句の出力形式は、実行する結合の種類によって異なります。The shape of the output of a join clause depends on the specific type of join you are performing. 最も一般的な 3 種類の結合を以下に示します。The following are three most common join types:

  • 内部結合Inner join

  • グループ結合Group join

  • 左外部結合Left outer join

内部結合Inner Join

次の例は、単純な内部等結合を示しています。The following example shows a simple inner equijoin. このクエリによって "商品名/カテゴリ" のペアからなるフラットなシーケンスが生成されます。This query produces a flat sequence of "product name / category" pairs. 複数の要素に同じカテゴリ文字列が含まれます。The same category string will appear in multiple elements. categories の要素に一致する products がない場合、そのカテゴリは結果に含まれません。If an element from categories has no matching products, that category will not appear in the results.

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

詳細については、「方法: 内部結合を実行する」を参照してください。For more information, see How to: Perform Inner Joins.

Group JoinGroup Join

into 式を使用した join 句はグループ結合と呼ばれます。A join clause with an into expression is called a group 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 つ以上の要素が関連付けられています。A group join produces a hierarchical result sequence, which associates elements in the left source sequence with one or more matching elements in the right side source sequence. リレーショナル データベースにおいてグループ結合に相当する用語はありません。グループ結合とは、本質的にはオブジェクト配列のシーケンスです。A group join has no equivalent in relational terms; it is essentially a sequence of object arrays.

右側のソース シーケンスの要素に左側のソースの要素と一致するものが見つからない場合は、その項目に対して join 句によって空の配列が生成されます。If no elements from the right source sequence are found to match an element in the left source, the join clause will produce an empty array for that item. つまりグループ結合は、結果シーケンスがグループに整理されることを除けば、基本的には内部等結合です。Therefore, the group join is still basically an inner-equijoin except that the result sequence is organized into groups.

グループ結合の結果を選択しただけでは、項目にアクセスすることはできでも、その照合に使用するキーを特定することはできません。If you just select the results of a group join, you can access the items, but you cannot identify the key that they match on. そのため、通常はグループ結合の結果を選択し、前の例に示したようなキー名を含む新しい型にするとさらに便利になります。Therefore, it is generally more useful to select the results of the group join into a new type that also has the key name, as shown in the previous example.

また、当然ながらグループ結合の結果を別のサブクエリのジェネレーターとして使用することもできます。You can also, of course, use the result of a group join as the generator of another subquery:

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;

詳細については、「方法: グループ化結合を実行する」を参照してください。For more information, see How to: Perform Grouped Joins.

左外部結合Left Outer Join

左外部結合では、右側のシーケンスに一致する要素がなくても、左側のソース シーケンスのすべての要素が返されます。In a left outer join, all the elements in the left source sequence are returned, even if no matching elements are in the right sequence. LINQLINQ で左外部結合を実行するには、DefaultIfEmpty メソッドとグループ結合を組み合わせて使用し、左側の要素に一致するものがない場合に既定の右側の要素を生成するように指定します。To perform a left outer join in LINQLINQ, use the DefaultIfEmpty method in combination with a group join to specify a default right-side element to produce if a left-side element has no matches. 参照型用の既定値として null を使用するか、ユーザー定義の既定の型を指定できます。You can use null as the default value for any reference type, or you can specify a user-defined default type. 次の例では、ユーザー定義の既定の型を示しています。In the following example, a user-defined default type is shown:

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 };

詳細については、「方法: 左外部結合を実行する」を参照してください。For more information, see How to: Perform Left Outer Joins.

等値演算子The equals operator

join 句は等結合を実行します。A join clause performs an equijoin. つまり、基準にできるのは 2 つのキーの等価性に関する照合のみです。In other words, you can only base matches on the equality of two keys. "~より大きい" や "等しくない" など、他の種類の比較はサポートされていません。Other types of comparisons such as "greater than" or "not equals" are not supported. すべての結合が等結合であることを明確化するために、join 句では == 演算子ではなく equals キーワードを使用します。To make clear that all joins are equijoins, the join clause uses the equals keyword instead of the == operator. equals キーワードは join 句でしか使用できず、また == 演算子とは 1 つの重要な点で異なります。The equals keyword can only be used in a join clause and it differs from the == operator in one important way. equals を指定すると、左側のキーでは外部のソース シーケンス、右側のキーでは内部のソースが使用されます。With equals, the left key consumes the outer source sequence, and the right key consumes the inner source. 外部のソースは equals の左側のスコープ内、内部のソース シーケンスは右側のスコープ内でのみ使用できます。The outer source is only in scope on the left side of equals and the inner source sequence is only in scope on the right side.

非等結合Non-Equijoins

複数の from 句を使用して新しいシーケンスをクエリに個別に導入することで、非等結合、クロス結合、およびその他のカスタム結合操作を実行できます。You can perform non-equijoins, cross joins, and other custom join operations by using multiple from clauses to introduce new sequences independently into a query. 詳細については、「方法: カスタム結合操作を実行する」を参照してください。For more information, see How to: Perform Custom Join Operations.

オブジェクト コレクションとリレーショナル テーブルでの結合の比較Joins on object collections vs. relational tables

LINQLINQ クエリ式での結合操作は、オブジェクト コレクションに対して実行されます。In a LINQLINQ query expression, join operations are performed on object collections. 2 つのリレーショナル テーブルの "結合" とまったく同じ方法でオブジェクト コレクションを結合することはできません。Object collections cannot be "joined" in exactly the same way as two relational tables. LINQLINQ では、2 つのソース シーケンスがリレーションシップによって関連付けられていない場合にのみ明示的な join 句が必要になります。In LINQLINQ, explicit join clauses are only required when two source sequences are not tied by any relationship. LINQ to SQLLINQ to SQL を使用する場合、外部キー テーブルはオブジェクト モデル内でプライマリ テーブルのプロパティとして表されます。When working with LINQ to SQLLINQ to SQL, foreign key tables are represented in the object model as properties of the primary table. たとえば Northwind データベースでは、Customer テーブルに Orders テーブルとの外部キー リレーションシップがあります。For example, in the Northwind database, the Customer table has a foreign key relationship with the Orders table. テーブルをオブジェクト モデルに割り当てると、Customer クラスには、その Customer に関連付けられた Orders のコレクションを含む Orders プロパティが含まれます。When you map the tables to the object model, the Customer class has an Orders property that contains the collection of Orders associated with that Customer. 実質的には、既に結合が実行されていることになります。In effect, the join has already been done for you.

LINQ to SQLLINQ to SQL を使用した関連テーブル間でのクエリの詳細については、「方法: データベース リレーションシップを割り当てる」を参照してください。For more information about querying across related tables in the context of LINQ to SQLLINQ to SQL, see How to: Map Database Relationships.

複合キーComposite Keys

複合キーを使用すると、複数の値の等価性をテストできます。You can test for equality of multiple values by using a composite key. 詳細については、「方法: 複合キーを使用して結合する」を参照してください。For more information, see How to: Join by Using Composite Keys. 複合キーは、group 句でも使用できます。Composite keys can be also used in a group clause.

Example

次の例では、同じ照合キーを使用し、同じデータ ソースでの内部結合、グループ結合、左外部結合の結果を比較しています。The following example compares the results of an inner join, a group join, and a left outer join on the same data sources by using the same matching keys. これらの例には、結果をコンソールにわかりやすく表示するためのコードがいくつか追加されています。Some extra code is added to these examples to clarify the results in the console display.

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.
*/

コメントRemarks

join 句の後に into がない場合は、Join メソッド呼び出しに変換されます。A join clause that is not followed by into is translated into a Join method call. join 句の後に into がある場合は、GroupJoin メソッド呼び出しに変換されます。A join clause that is followed by into is translated to a GroupJoin method call.

関連項目See Also

クエリ キーワード (LINQ)Query Keywords (LINQ)
LINQ クエリ式LINQ Query Expressions
結合演算Join Operations
group 句group clause
方法: 左外部結合を実行するHow to: Perform Left Outer Joins
方法: 内部結合を実行するHow to: Perform Inner Joins
方法: グループ化結合を実行するHow to: Perform Grouped Joins
方法: join 句の結果の順序を指定するHow to: Order the Results of a Join Clause
方法: 複合キーを使用して結合するHow to: Join by Using Composite Keys
方法 : サンプル データベースをインストールするHow to: Install Sample Databases