内部結合の実行

リレーショナル データベースでは、"内部結合" により、2 番目のコレクション内の一致するすべての要素に対して、最初のコレクションの各要素が一度表示される結果セットが生成されます。 最初のコレクション内の要素に一致する要素が存在しない場合、その要素は結果セットには表示されません。 Join メソッドは、C# の join 句によって呼び出され、内部結合を実装します。

この記事では、次の 4 種類の内部結合を実行する方法を示します。

  • 簡単なキーに基づいて、2 つのデータ ソースの要素を関連付ける単純な内部結合。

  • "複合" キーに基づいて、2 つのデータ ソースの要素を関連付ける内部結合。 複合キーは複数の値で構成され、複数のプロパティに基づいて要素を関連付けることができます。

  • 一連の結合操作が相互に追加された "複数の結合"。

  • グループ結合を使用して実装された内部結合。

注意

このトピックの例には、次のデータ クラスが使用されています。

record Person(string FirstName, string LastName);
record Pet(string Name, Person Owner);
record Employee(string FirstName, string LastName, int EmployeeID);
record Cat(string Name, Person Owner) : Pet(Name, Owner);
record Dog(string Name, Person Owner) : Pet(Name, Owner);

オブジェクトのコレクションの照会」からの Student クラス。

例 - 簡単なキーの結合

次の例は、2 つのユーザー定義型オブジェクト、PersonPet が含まれた 2 つのコレクションを作成します。 クエリでは、C# の join 句を使用して、Person オブジェクトを Owner がこの Person である Pet オブジェクトを照合します。 C# の select 句では、結果のオブジェクトの表示内容を定義します。 この例では、結果のオブジェクトは、飼い主の姓とペットの名前で構成される匿名型です。

Person magnus = new(FirstName: "Magnus", LastName: "Hedlund");
Person terry = new("Terry", "Adams");
Person charlotte = new("Charlotte", "Weiss");
Person arlene = new("Arlene", "Huff");
Person rui = new("Rui", "Raposo");

List<Person> people = new() { magnus, terry, charlotte, arlene, rui };

List<Pet> pets = new()
{
    new(Name: "Barley", Owner: terry),
    new("Boots", terry),
    new("Whiskers", charlotte),
    new("Blue Moon", rui),
    new("Daisy", magnus),
};

// Create a collection of person-pet pairs. Each element in the collection
// is an anonymous type containing both the person's name and their pet's name.
var query =
    from person in people
    join pet in pets on person equals pet.Owner
    select new
    {
        OwnerName = person.FirstName,
        PetName = pet.Name
    };

foreach (var ownerAndPet in query)
{
    Console.WriteLine($"\"{ownerAndPet.PetName}\" is owned by {ownerAndPet.OwnerName}");
}

/* Output:
     "Daisy" is owned by Magnus
     "Barley" is owned by Terry
     "Boots" is owned by Terry
     "Whiskers" is owned by Charlotte
     "Blue Moon" is owned by Rui
*/

LastName が "Huff" の Person オブジェクトは、Pet.Owner がその Person に等しい Pet オブジェクトがないため、結果セットに表示されません。

例 - 複合キーの結合

1 つのプロパティだけに基づいて要素を関連付ける代わりに、複合キーを使用して、複数のプロパティに基づいて要素を比較できます。 これを行うには、各コレクションに対してキー セレクター関数を指定し、比較するプロパティで構成された匿名型を返します。 プロパティにラベルを付ける場合は、各キーの匿名型に同じラベルを付ける必要があります。 また、プロパティは、同じ順序で表示する必要があります。

次の例は、Employee オブジェクトのリストと Student オブジェクトのリストを使用して、学生でもある社員を調べます。 これらの型の両方に、String 型の FirstName プロパティと LastName プロパティがあります。 それぞれのリストの要素から結合キーを作成する関数が、各要素の FirstName プロパティと LastName プロパティで構成された匿名型を返します。 結合操作により、これらの複合キーが等しいかどうか比較され、それぞれのリストの氏名が一致するオブジェクトのペアが返されます。

List<Employee> employees = new()
{
    new(FirstName: "Terry", LastName: "Adams", EmployeeID: 522459),
    new("Charlotte", "Weiss", 204467),
    new("Magnus", "Hedland", 866200),
    new("Vernette", "Price", 437139)
};

List<Student> students = new()
{
    new(FirstName: "Vernette", LastName: "Price", StudentID: 9562),
    new("Terry", "Earls", 9870),
    new("Terry", "Adams", 9913)
};

// Join the two data sources based on a composite key consisting of first and last name,
// to determine which employees are also students.
var query =
    from employee in employees
    join student in students on new
    {
        employee.FirstName,
        employee.LastName
    } equals new
    {
        student.FirstName,
        student.LastName
    }
    select employee.FirstName + " " + employee.LastName;

Console.WriteLine("The following people are both employees and students:");
foreach (string name in query)
{
    Console.WriteLine(name);
}

/* Output:
    The following people are both employees and students:
    Terry Adams
    Vernette Price
 */

例 - 複数の結合

任意の数の結合操作を相互に追加して、複数の結合を実行できます。 C# の各 join 句は、指定されたデータ ソースを前の結合の結果に関連付けます。

次の例は、Person オブジェクトのリスト、Cat オブジェクトのリスト、Dog オブジェクトのリストの 3 つのコレクションを作成します。

C# の最初の join 句では、Cat.Owner と一致する Person オブジェクトに基づいて飼い主と猫を一致させます。 この操作で、Person オブジェクトと Cat.Name が含まれた匿名型のシーケンスが返されます。

C# の 2 番目の join 句では、Person 型の Owner プロパティと動物の名前の最初の文字で構成される複合キーに基づいて、最初の結合で返された匿名型を、指定された犬のリストの Dog オブジェクトに関連付けます。 この操作で、一致するそれぞれのペアの Cat.Name プロパティと Dog.Name プロパティが含まれた匿名型のシーケンスが返されます。 これは内部結合であるため、2 番目のデータ ソースに一致するものが存在する、最初のデータ ソースのオブジェクトのみが返されます。

Person magnus = new(FirstName: "Magnus", LastName: "Hedlund");
Person terry = new("Terry", "Adams");
Person charlotte = new("Charlotte", "Weiss");
Person arlene = new("Arlene", "Huff");
Person rui = new("Rui", "Raposo");
Person phyllis = new("Phyllis", "Harris");

List<Person> people = new() { magnus, terry, charlotte, arlene, rui, phyllis };

List<Cat> cats = new()
{
    new(Name: "Barley", Owner: terry),
    new("Boots", terry),
    new("Whiskers", charlotte),
    new("Blue Moon", rui),
    new("Daisy", magnus),
};

List<Dog> dogs = new()
{
    new(Name: "Four Wheel Drive", Owner: phyllis),
    new("Duke", magnus),
    new("Denim", terry),
    new("Wiley", charlotte),
    new("Snoopy", rui),
    new("Snickers", arlene),
};

// The first join matches Person and Cat.Owner from the list of people and
// cats, based on a common Person. The second join matches dogs whose names start
// with the same letter as the cats that have the same owner.
var query =
    from person in people
    join cat in cats on person equals cat.Owner
    join dog in dogs on new
    {
        Owner = person,
        Letter = cat.Name.Substring(0, 1)
    } equals new
    {
        dog.Owner,
        Letter = dog.Name.Substring(0, 1)
    }
    select new
    {
        CatName = cat.Name,
        DogName = dog.Name
    };

foreach (var obj in query)
{
    Console.WriteLine(
        $"The cat \"{obj.CatName}\" shares a house, and the first letter of their name, with \"{obj.DogName}\"."
    );
}

/* Output:
     The cat "Daisy" shares a house, and the first letter of their name, with "Duke".
     The cat "Whiskers" shares a house, and the first letter of their name, with "Wiley".
 */

例 - グループ化結合を使用した内部結合

グループ結合を使用して内部結合を実装する方法を次の例に示します。

query1 で、Person オブジェクトのリストは、Pet.Owner プロパティと一致する Person に基づいて、Pet オブジェクトのリストにグループ結合されます。 グループ結合によって、それぞれのグループが Person オブジェクトおよび一致する Pet オブジェクトのシーケンスで構成された、中間グループのコレクションが作成されます。

2 番目の from 句をクエリに追加すると、シーケンスのシーケンスが 1 つの長いシーケンスに結合 (または平坦化) されます。 最後のシーケンスの要素の型は、select 句で指定されます。 この例では、この型は、一致する各ペアの Person.FirstName プロパティと Pet.Name プロパティで構成された匿名型です。

query1 の結果は、into 句のない join 句を使用して内部結合を実行することで得られた結果セットと同じです。 query2 変数は、これと同等のクエリを示しています。

Person magnus = new(FirstName: "Magnus", LastName: "Hedlund");
Person terry = new("Terry", "Adams");
Person charlotte = new("Charlotte", "Weiss");
Person arlene = new("Arlene", "Huff");

List<Person> people = new() { magnus, terry, charlotte, arlene };

List<Pet> pets = new()
{
    new(Name: "Barley", Owner: terry),
    new("Boots", terry),
    new("Whiskers", charlotte),
    new("Blue Moon", terry),
    new("Daisy", magnus),
};

var query1 =
    from person in people
    join pet in pets on person equals pet.Owner into gj
    from subpet in gj
    select new
    {
        OwnerName = person.FirstName,
        PetName = subpet.Name
    };

Console.WriteLine("Inner join using GroupJoin():");
foreach (var v in query1)
{
    Console.WriteLine($"{v.OwnerName} - {v.PetName}");
}

var query2 =
    from person in people
    join pet in pets on person equals pet.Owner
    select new
    {
        OwnerName = person.FirstName,
        PetName = pet.Name
    };

Console.WriteLine();
Console.WriteLine("The equivalent operation using Join():");
foreach (var v in query2)
{
    Console.WriteLine($"{v.OwnerName} - {v.PetName}");
}

/* Output:
    Inner join using GroupJoin():
    Magnus - Daisy
    Terry - Barley
    Terry - Boots
    Terry - Blue Moon
    Charlotte - Whiskers

    The equivalent operation using Join():
    Magnus - Daisy
    Terry - Barley
    Terry - Boots
    Terry - Blue Moon
    Charlotte - Whiskers
*/

こちらもご覧ください