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 절은 두 개의 소스 시퀀스를 입력으로 사용합니다.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. 다음은 세 가지 가장 일반적인 조인 유형입니다.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 Perform inner joins.

그룹 조인Group 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 };

그룹 조인은 왼쪽 소스 시퀀스의 요소를 오른쪽 소스 시퀀스에서 일치하는 하나 이상의 요소와 연결하는 계층적 결과 시퀀스를 생성합니다.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 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. LINQ에서 왼쪽 우선 외부 조인을 수행하려면 그룹 조인과 함께 DefaultIfEmpty 메서드를 사용하여 왼쪽 요소에 일치하는 요소가 없을 경우 생성할 기본 오른쪽 요소를 지정합니다.To perform a left outer join in LINQ, 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 Perform left outer joins.

같음 연산자The equals operator

join 절은 동등 조인을 수행합니다.A join clause performs an equijoin. 즉, 일치 항목만을 기준으로 두 키가 같은지 비교할 수 있습니다.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 절에서만 사용할 수 있으며 한 가지 중요한 측면에서 == 연산자와 다릅니다.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 Perform custom join operations.

개체 컬렉션 및 관계형 테이블에서 조인Joins on object collections vs. relational tables

LINQ 쿼리 식에서 조인 작업은 개체 컬렉션에서 수행됩니다.In a LINQ query expression, join operations are performed on object collections. 개체 컬렉션은 두 개의 관계형 테이블과 정확히 동일한 방식으로 "조인"할 수 없습니다.Object collections cannot be "joined" in exactly the same way as two relational tables. LINQ에서는 두 소스 시퀀스가 관계로 연결되지 않는 경우에만 명시적 join 절이 필요합니다.In LINQ, 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 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);
        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:");
            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

뒤에 into가 오지 않는 join 절은 Join 메서드 호출로 변환됩니다.A join clause that is not followed by into is translated into a Join method call. 뒤에 into가 오는 join 절은 GroupJoin 메서드 호출로 변환됩니다.A join clause that is followed by into is translated to a GroupJoin method call.

참고 항목See also