멈추지 않고 끈질기게

[DB][SQL] 조인(JOIN) 본문

DB

[DB][SQL] 조인(JOIN)

sam0308 2023. 5. 18. 11:03

※ 해당 포스팅은 개인의 공부 정리용 글입니다. 틀린 내용이 있다면 추후 수정될 수 있습니다.

※ 해당 포스팅은 Mycrosoft SQL Server에서 사용하는 T-SQL 문법을 기반으로 하고 있으며, 예시는 SSMS(SQL Server Management Studio) 환경에서 작성하고 있습니다. 

 

 

 

 

이번 포스팅에서는 두 테이블을 합치는 조인(JOIN)에 대해 알아보겠습니다.

1. CROSS JOIN(교차 결합)

 CROSS JOIN두 테이블(릴레이션)의 카티션 프로덕트를 반환하는 키워드입니다. 즉, N행의 테이블과 M행의 테이블을 CROSS JOIN 할 경우 N x M 행의 테이블을 출력합니다. 다음은 CROSS JOIN의 사용 예시입니다.

-- players 테이블과 ranks 테이블의
-- 카티션 프로덕트 결과 반환
select *
from players
	CROSS JOIN ranks;

그림 1. CROSS JOIN 결과

 그림 1의 좌측 상단이 players 테이블, 좌측 하단이 ranks 테이블입니다. 각각 3행, 4행 밖에 안되는 작은 테이블이지만 CROSS JOIN 결과는 3 x 4 = 12행인 것을 확인할 수 있습니다. 테이블의 크기에 따라 연산 결과가 엄청나게 커질 수 있으므로, 사전에 결과 테이블의 크기를 예상하고 사용해야 합니다(여담이지만 샘플용 baseballDB에 존재하는 두 테이블간의 CROSS JOIN을 시도했다가, 쿼리가 끝나질 않아서 강제중단한 경험이 있습니다).

 

 또한 CROSS JOIN은 단순히 한쪽 테이블의 모든 행에 다른 테이블의 데이터를 접목시킨 것이기 때문에, 위와 같이 특별한 목적 없이 사용할 경우 유의미한 정보를 도출해내기 어렵습니다. 따라서 CROSS JOIN은 사용 전에 두 테이블의 카티션 프로덕트 결과에서 유의미한 정보를 도출해낼 수 있을지 고민한 후에 사용하는 것이 좋습니다.

 

 

 

2. INNER JOIN(내부 결합)

 INNER JOIN은 두 테이블(릴레이션)의 내부 조인 결과를 반환하는 키워드입니다. 두 테이블에서 모두 존재하는 데이터만을 포함하며, 결합 기준을 ON 절에 작성해주어야 합니다. 다음은 INNER JOIN 사용 예시입니다.

-- 동일한 playerID 기준으로 
-- players 테이블과 ranks 테이블 결합
select *
from players
	INNER JOIN ranks 
	ON players.playerID = ranks.playerID;

그림 2. INNER JOIN 결과

 players 테이블과 ranks 테이블의 INNER JOIN을 실행하였고, 결합 기준은 동일한 playerID로 설정했습니다. 실행 결과를 보면 players 테이블과 ranks 테이블에 모두 존재하는 playerID(1001, 1002)의 데이터만 남은 것을 확인할 수 있습니다. 이처럼 INNER JOIN은 결합 기준의 속성값이 양쪽 테이블에 모두 존재하는 데이터만을 반환합니다. 그림 2의 실행 결과를 보면 'v최강전사v'가 랭킹 1위, '왼손은활들뿐'이 랭킹 4위 유저임을 알 수 있습니다.

 

 다만 INNER JOIN의 경우 양쪽에 모두 존재하는 데이터만 포함하므로, 결합 시 어느 한쪽에 비중을 둘 수 없습니다. 예를 들어 players 테이블에 있는 모든 유저들의 랭킹과 전투력(CP, Combat Point)을 알기 위해 조인할 경우, INNER JOIN으로는 랭킹 테이블에 들지 못한(playerID 1003) 유저에 대한 데이터는 얻을 수 없습니다. 한쪽 테이블을 기준으로 결합하고 싶다면 후술할 OUTER JOIN을 사용해야 합니다.

 

 

 

3. OUTER JOIN(외부 결합)

 OUTER JOIN은 두 테이블(릴레이션)의 외부 조인을 나타내며, SQL에서는 LEFT JOINRIGHT JOIN 키워드로 사용합니다. 외부 조인은 내부 조인과 달리 한쪽 테이블의 정보를 모두 포함하고, 반대쪽 테이블에 없는 정보는 NULL로 처리합니다. 예를 들어 LEFT JOIN 시 좌측 테이블의 정보는 모두 포함하며, 우측 테이블에 없는 정보는 NULL 값으로 처리합니다. 참고로 좌우는 쿼리 상에서 from 절에 작성하는 테이블이 좌측, JOIN 절에 기재하는 테이블이 우측에 해당합니다.

-- players 테이블을 기준으로 결합(LEFT JOIN)
select *
from players
	LEFT JOIN ranks 
	ON players.playerID = ranks.playerID;

-- ranks 테이블을 기준으로 결합(RIGHT JOIN)
select *
from players
	RIGHT JOIN ranks 
	ON players.playerID = ranks.playerID;

그림 3. (상) LEFT JOIN, (하) RIGHT JOIN

 위 예시는 INNER JOIN의 예시와 동일한 쿼리를 LEFT JOIN과 RIGHT JOIN으로 실행한 것입니다. LEFT JOIN 결과를 보면 players 테이블의 모든 유저 데이터를 포함하고 있으며, rank 테이블에 데이터가 없는 유저의 경우 랭킹 관련 데이터가 전부 NULL로 출력되고 있습니다. 반대로 RIGHT JOIN의 결과에서는 rank 테이블의 모든 데이터를 포함하고 있으며, players 테이블에 데이터가 없는 playerID(1007, 1023)의 경우 유저 관련 데이터가 NULL 값으로 출력되고 있습니다.

 

 INNER JOIN과 달리 결합하는 두 테이블 중 한쪽에 비중을 실어주는 결합 방식으로, INNER JOIN으로 얻을 수 없는 정보를 얻을 수 있습니다. 예를 들어 위 예시의 LEFT JOIN 결과의 경우, players 테이블에 있는 모든 유저들의 랭킹 정보를 확인할 수 있습니다. 또한 랭킹 정보가 없는 '치유의근원' 유저는 랭킹 테이블에 들지 못했음을 확인할 수 있습니다. RIGHT JOIN 결과를 살펴보면 rank 테이블에 있는 모든 유저들의 유저 정보(닉네임, 길드)를 확인할 수 있고, playerID 1007, 1023인 유저의 경우 현재 players 테이블에 데이터가 없다는 것을 확인할 수 있습니다.

 


 

 이처럼 JOIN을 이용하면 두 테이블에 나누어 저장된 데이터들을 한데 모아 확인할 수 있습니다. 관계형 데이터베이스에서는 데이터를 여러개의 테이블에 분산하여 저장하므로, 각 조인 방식들의 특징을 파악하고 내가 원하는 정보를 얻기 위해 적절한 JOIN을 선택할 줄 알아야겠습니다.