본문 바로가기

Programming/Microsoft SQL Server

[SQL] Table Join

Table을 Join한다는 것은 두개 이상의 Table을 하나로 묶어서 조회하는 것을 의미합니다.

1. Inner Join

Join된 테이블에서 on조건에 해당하는 것만을 조회합니다.

Select BusinessEntityID, A.DepartmentID, Name, GroupName
From HumanResources.EmployeeDepartmentHistory As A Inner Join HumanResources.Department As B
On A.DepartmentID = B.DepartmentID
Order By DepartmentID

HumanResources.EmployeeDepartmentHistory Table과 HumanResources.Department Table을 Join하여 각 Table의 DepartmentID가 같은것만을 대상으로(On절) 조회합니다. 이때 HumanResources.EmployeeDepartmentHistory Table의 BusinessEntityID와 DepartmentID 그리고 HumanResources.Department Table의 Name과 GroupName을 조회 대상으로 합니다.


위의 Query에서 HumanResources.EmployeeDepartmentHistory As A 부분과 HumanResources.Department As B에서의 As는 각 Table에 이름을 지정한 것입니다. 즉 HumanResources.EmployeeDepartmentHistory Table의 이름을 A라 하고 HumanResources.Department Table의 이름을 B라고 정의한 것이죠.

이렇게 정의된 이름은 Select 구문에서 처럼 A.DepartmentID 라고 작성하여 A Table에 있는 DepartmentID열을 가져온다고 명시하고 있습니다.

그렇다면 왜 이름을 정의하는 것이 필요할까요?

이름을 지정해 주지 않으면 On으로 조건을 지정할때나 특정 열을 조회할때 Join된 Table각각에서 공통된 열이름이 존재할 경우 "어떤 Table의 열을 가져와야 하는가?"라는 문제가 발생하게 됩니다.

위 예제에서도 HumanResources.EmployeeDepartmentHistory Table과 HumanResources.Department Table은 공통적으로 DepartmentID열을 가지고 있는데 이때 On에서 On DepartmentID = DepartmentID 라고만 하게 되면 각각 어떤 Table에 있는 DepartmentID열을 의미하는지 알 수 없게 됩니다.

뿐만 아니라 Selet에서 A.DepartmentID가 아닌 DepartmentID라고만 했을 경우에도 위와 같은 똑같은 문제가 발생할 수 있는 것입니다.

따라서 이 문제를 해결하려면 각각의 Table에 이름을 지정하여 어떤 Table을 기준으로 하는지 명시함으로서 공통된 열을 구분하는 것입니다.

반면 BusinessEntityID나 Name, GroupName의 경우에는 각 Table에 중복되지 않게 존재하므로 반드시 별칭(이름) 지정이 필요하지는 않습니다.

물론 As구문을 통해서 반드시 별칭을 지정해야만 중복된 열에 대한 문제를 해결할 수 있는것은 아닙니다. 다음 예제처럼 Query처럼 작성해도 별칭을 지정한 것과 결과는 같습니다.

Select BusinessEntityID, HumanResources.EmployeeDepartmentHistory.DepartmentID, Name, GroupName
From HumanResources.EmployeeDepartmentHistory Inner Join HumanResources.Department
On HumanResources.EmployeeDepartmentHistory.DepartmentID = HumanResources.Department.DepartmentID
Order By DepartmentID

보시는 바와 같이 As를 통해 이름을 지정해 주지 않고도 중복되는 열마다 Table명칭 자체를 사용해 어떤 Table에 있는 열인지를 명확히 하고 있습니다. 하지만 이렇게 일일이 Table이름을 붙이게 되면 유지보수에도 힘들뿐더러 단순히 보기에도 매우 복잡해 보이므로(이게 좋다고 하면 할말이 없지만) 비효휼 적이라는 것을 알 수 있습니다.

참고:
Table Join에는 단순히 2개까지의 Table만 Join이 가능한 것은 아닙니다. 이론상 최대 256개 까지 가능하지만 실제로는 많아봐야 3~4개 정도의 테이블에만 Join에 사용하곤 합니다. 만일 Join대상이 그 이상이라면 Join대신 다른 방법을 찾는것이 경우에 따라 더 현명할 수 있으며 Join대상이 너무 많은 경우에는 Table 재설계를 고려해봐야 합니다.

Select A.BusinessEntityID, C.LoginID, A.DepartmentID, Name, GroupName
From HumanResources.EmployeeDepartmentHistory As A
 Inner Join HumanResources.Department As B
On A.DepartmentID = B.DepartmentID
 Inner Join HumanResources.Employee As C
On A.BusinessEntityID = C.BusinessEntityID
Order By BusinessEntityID

HumanResources.EmployeeDepartmentHistory Table과 HumanResources.Department Table을 DepartmentID별로 Join하고 다시 HumanResources.EmployeeDepartmentHistory Table과 HumanResources.Employee Table을 BusinessEntityID별로 Join합니다.


Table을 두개이상 Join할경우 On절을 통해 Join할 조건을 작성하고 그 다음 Join문에 On조건 작성을 다시 반복합니다. 또한 위와 같은 방식으로 경우에 따라 여러개의 Table을 Join할 수 있습니다.

2. Left Outer Join

Left Outer Join은 두개 이상의 Table을 조인하여 Data를 조회하되 왼쪽(Left)에 있는 Table의 내용은 모두 가져오도록 합니다. 이때 On조건에 부합하는 것은 정상적으로 조회하고 조건에 부합하지 않는 경우에는 Null로 표시됩니다.

Select A.BusinessEntityID, B.AddressID, B.AddressTypeID
From Person.Person As A Left Outer Join Person.BusinessEntityAddress As B
On A.BusinessEntityID = B.BusinessEntityID
Order By BusinessEntityID

Person.Person Table과 Person.BusinessEntityAddress Table을 BusinessEntityID별로 Join하여 조회하도록 합니다. 단 Person.Person Table의 내용은 빠짐없이 모두 가져오도록 합니다.


BusinessEntityID의 1785나 1786등의 값은 Person.BusinessEntityAddress Table에 존재하지 않기 때문에 On조건에 따라 AddressID, AddressTypeID는 조회할 수 없습니다. 하지만 Left Outer Join을 통해 Person.Person Table의 내용은 모두 가져오도록 하였으므로 표시할 수 없는 AddressID, AddressTypeID열값은 Null로 나타내는 것입니다.

또한 Left Outer Join에서 구문이 너무 길다고 생각되시면 줄여서 Left Join이라고 할 수도 있습니다.

Select A.BusinessEntityID, B.AddressID, B.AddressTypeID
From Person.Person As A Left Join Person.BusinessEntityAddress As B
On A.BusinessEntityID = B.BusinessEntityID
Order By BusinessEntityID

3. Right Outer Join

Left Outer Join과 반대로 오른쪽에 조인된 Table을 기준으로 모든 Data를 가져오도록 합니다. 물론 이때에도 표시할 수 없는 값은 null로 나타납니다.

Select A.ProductID, A.StandardCost, B.Name
From Production.ProductCostHistory As A Right Outer Join Production.Product As B
On A.ProductID = B.ProductID

Production.ProductCostHistory Table과 Production.Product Table을 ProductID별로 Join하여 조회하되 Production.Product Table의 모든행을 전부 가져오도록 합니다.


4. Full Outer Join

Full Outer Join은 간단히 말해 Left Outer Join과 Right Outer Join을 합한것입니다. 즉, 양쪽 Table의 모든 Data를 다 가져오도록 하는 것입니다. 물론 이때에도 표시할 수 없는 값은 Null이 됩니다.

Select ProductID, ProductDescriptionID, StandardCost, Description
From Production.ProductCostHistory Full Outer Join Production.ProductDescription
On ProductID = ProductDescriptionID

Production.ProductCostHistory Table과 Production.ProductDescription Table을 Join하되 양쪽 Table의 모든 행을 조회합니다.


5. Cross Join

왼쪽 Table에서 오른쪽 Table을 제곱하여 그 결과를 표시하도록 합니다. 다시 말해 왼쪽 Table 한행당 오른쪽 Table전체를 그리고 다시 왼쪽 Table 한행당 오른쪽 Table 전체... 이런한 방법으로 Join된 결과를 표시하는 것입니다.

Select A.ProductID, A.Name, StartDate, EndDate, B.StandardCost
From Production.Product As A Cross Join Production.ProductCostHistory As B
Order By A.ProductID


결과를 보시면 조회된 총 행의 갯수가 199,080행입니다. Production.Product Table의 행이 504행 Production.ProductCostHistory Table의 행이 395행이므로 199080 = 504 * 395이 되는 것입니다.

주의:
이처럼 경우에 따라 엄청난 수의 Data를 조회하므로 Cross Join 사용시 성능면에서 각별히 주의해야 합니다.

6. Salf Join

Salf Join은 단순히 자기 자신을 Join하는 경우를 말합니다. 이는 하나의 Table안에서 서로 연관된 Data를 조회하고자 할때 쓰이지만 그리 흔하게 쓰이는 방법은 아닙니다.

Select A.BusinessEntityID, A.LoginID, A.JobTitle, B.BusinessEntityID, B.LoginID, B.JobTitle
From HumanResources.Employee As A Inner Join HumanResources.Employee As B
On A.BusinessEntityID = B.OrganizationLevel
Order By A.BusinessEntityID

HumanResources.Employee Table자신을 Join하여 BusinessEntityID 열과 OrganizationLevel 열값이 같은 것끼리  조회 하도록 합니다.


7. Where 절을 이용한 Table Join

두개 이상의 Table을 Join하는데는 굳이 Join문을 이용하지 않고도 Where절을 이용하는 방법이 있습니다.

Select ProductID, ProductDescriptionID, StandardCost, Description
From Production.ProductCostHistory, Production.ProductDescription
Where ProductID = ProductDescriptionID

Production.ProductCostHistory Table과 Production.ProductDescription Table의 내용을 조회하되 ProductID = ProductDescriptionID같은 것만 조회하도록 합니다.

Select ProductID, ProductDescriptionID, StandardCost, Description
From Production.ProductCostHistory Inner Join Production.ProductDescription
On ProductID = ProductDescriptionID

Production.ProductCostHistory Table과 Production.ProductDescription Table을 ProductID와 ProductDescriptionID가 같은 것끼리 Join하여 조회하도록 합니다.


Where절을 이용하여 Join하고자 하는 경우에는 Join문은 쓰지 않으며 Join할 각 Table을 ,를 통해 구분하면 됩니다. 이때 사용하는 Where절은 Join시에 사용하던 On과 같은 역활을 하게 됩니다.