# SQL 다중 테이블 연산 다중 테이블 연산은 두 개 이상의 테이블을 사용하여 데이터를 조회하거나 조작하는 것을 의미한다. 다중 테이블 연산에는 `JOIN`, `UNION`, 서브쿼리 등이 있다. ## 테이블 및 데이터 준비 다중 테이블 연산을 알아보기 위해 간단한 테이블을 설계하고 데이터를 준비해보자. ```sql -- Employees 테이블 생성 CREATE TABLE Employees ( EmployeeID INT PRIMARY KEY, Name VARCHAR(100), DepartmentID INT ); -- Departments 테이블 생성 CREATE TABLE Departments ( DepartmentID INT PRIMARY KEY, DepartmentName VARCHAR(100) ); ``` ```sql -- Employees 데이터 삽입 INSERT INTO Employees (EmployeeID, Name, DepartmentID) VALUES (1, 'Alice', 1), (2, 'Bob', 1), (3, 'Carol', 2), (4, 'David', 4); -- David는 부서가 Departments 테이블에 존재하지 않음 -- Departments 데이터 삽입 INSERT INTO Departments (DepartmentID, DepartmentName) VALUES (1, 'HR'), (2, 'Marketing'), (3, 'IT'); -- IT 부서에는 직원이 없음 ``` ## `JOIN` ![image](https://hackmd.io/_uploads/B1DPV2mgC.png) `JOIN`은 두 개 이상의 테이블에서 관련된 데이터를 결합할 때 사용된다. 예를 들어, 고객 정보를 저장하는 테이블과 고객이 주문한 주문 정보를 저장하는 테이블이 따로 있다면, 이 두 테이블을 `JOIN`을 통해 연결하여 고객과 그들의 주문 정보를 함께 조회할 수 있다. `JOIN`의 종류는 여러가지가 있다. | JOIN 종류 | 설명 | | ------------ | ---------------------------------------------------------------------------------------- | | `INNER JOIN` | 두 테이블 사이에 일치하는 행만 반환한다. | | `LEFT JOIN` | 왼쪽 테이블의 모든 행을 반환하고, 오른쪽 테이블의 일치하는 행이 있는 경우 함께 반환한다. | | `RIGHT JOIN` | 오른쪽 테이블의 모든 행을 반환하고, 왼쪽 테이블의 일치하는 행이 있는 경우 함께 반환한다. | ### `INNER JOIN` `INNER JOIN`은 두 테이블 사이에 일치하는 행만 반환한다. `INNER JOIN`을 사용하는 방법은 다음과 같다. ```sql 첫 번째 테이블 INNER JOIN 두 번째 테이블 ON 조건 ``` `INNER JOIN`은 `INNER` 키워드를 생략할 수 있다. ```sql 첫 번째 테이블 JOIN 두 번째 테이블 ON 조건 ``` 예를 들어 위에서 생성한 `Employees`, `Departments` 테이블을 `INNER JOIN`하여 직원 이름과 부서 이름을 함께 조회하려면 다음과 같이 쿼리를 작성할 수 있다. ```sql SELECT Employees.Name, Departments.DepartmentName FROM Employees INNER JOIN Departments ON Employees.DepartmentID = Departments.DepartmentID; /* 결과 +-------+----------------+ | Name | DepartmentName | +-------+----------------+ | Alice | HR | | Bob | HR | | Carol | Marketing | +-------+----------------+ */ ``` 이 쿼리는 `Employees` 테이블과 `Departments` 테이블 사이에 `DepartmentID`가 일치하는 행만 반환한다. ### `LEFT JOIN` `LEFT JOIN`은 왼쪽 테이블의 모든 행을 반환하고, 오른쪽 테이블의 일치하는 행이 있는 경우 함께 반환한다. `LEFT JOIN`을 사용하는 방법은 다음과 같다. ```sql 첫 번째 테이블 LEFT JOIN 두 번째 테이블 ON 조건 ``` 예를 들어 위에서 생성한 `Employees`, `Departments` 테이블을 `LEFT JOIN`하여 직원 이름과 부서 이름을 함께 조회하려면 다음과 같이 쿼리를 작성할 수 있다. ```sql SELECT Employees.Name, Departments.DepartmentName FROM Employees LEFT JOIN Departments ON Employees.DepartmentID = Departments.DepartmentID; /* 결과 +-------+----------------+ | Name | DepartmentName | +-------+----------------+ | Alice | HR | | Bob | HR | | Carol | Marketing | | David | NULL | +-------+----------------+ */ ``` `INNER JOIN`과 달리 `LEFT JOIN`은 왼쪽 테이블의 모든 행을 반환하므로, `Employees` 테이블의 모든 직원 정보를 조회할 수 있다. 만약, 오른쪽 테이블인 `Departments` 테이블에 일치하는 데이터가 없는 경우에는 `NULL`로 표시된다. 여기서 `David`는 `Departments` 테이블에 존재하지 않는 부서에 속해 있기 때문에 `NULL`로 표시된다. ### `RIGHT JOIN` `RIGHT JOIN`은 오른쪽 테이블의 모든 행을 반환하고, 왼쪽 테이블의 일치하는 행이 있는 경우 함께 반환한다. `RIGHT JOIN`을 사용하는 방법은 다음과 같다. ```sql 첫 번째 테이블 RIGHT JOIN 두 번째 테이블 ON 조건 ``` 예를 들어 위에서 생성한 `Employees`, `Departments` 테이블을 `RIGHT JOIN`하여 직원 이름과 부서 이름을 함께 조회하려면 다음과 같이 쿼리를 작성할 수 있다. ```sql SELECT Employees.Name, Departments.DepartmentName FROM Employees RIGHT JOIN Departments ON Employees.DepartmentID = Departments.DepartmentID; /* 결과 +-------+----------------+ | Name | DepartmentName | +-------+----------------+ | Alice | HR | | Bob | HR | | Carol | Marketing | | NULL | IT | +-------+----------------+ */ ``` `RIGHT JOIN`은 `LEFT JOIN`과 반대로 오른쪽 테이블의 모든 행을 반환하므로, `Departments` 테이블의 모든 부서 정보를 조회할 수 있다. 만약, 왼쪽 테이블인 `Employees` 테이블에 일치하는 데이터가 없는 경우에는 `NULL`로 표시된다. 여기서 `IT` 부서에는 직원이 없기 때문에 `NULL`로 표시된다. ## `UNION` `UNION`은 두 개 이상의 `SELECT` 문의 결과를 결합할 때 사용된다. 이때 각각의 `SELECT` 문으로 선택된 필드의 개수와 타입은 모두 같아야 하며, 필드의 순서 또한 같아야 한다. `UNION`을 사용하는 방법은 다음과 같다. ```sql SELECT ... UNION SELECT ... ``` 예를 들어, `Employees` 테이블과 `Departments` 테이블에서 직원 이름과 부서 이름을 함께 조회하려면 다음과 같이 `UNION`을 사용할 수 있다. ```sql SELECT Name, NULL AS DepartmentName FROM Employees UNION SELECT NULL AS Name, DepartmentName FROM Departments; /* 결과 +-------+----------------+ | Name | DepartmentName | +-------+----------------+ | Alice | NULL | | Bob | NULL | | Carol | NULL | | David | NULL | | NULL | HR | | NULL | Marketing | | NULL | IT | +-------+----------------+ */ ``` ### `UNION ALL` `UNION ALL`은 `UNION`과 비슷하지만, 중복된 행을 제거하지 않고 모두 반환한다. `UNION ALL`을 사용하는 방법은 다음과 같다. ```sql SELECT ... UNION ALL SELECT ... ``` ## 서브쿼리 (Subquery) 쿼리문 내에서 다른 쿼리문(`SELECT` 문)을 사용할 수 있다. 이 때, 외부 쿼리와 내부 쿼리의 관계에 따라 서브쿼리(Subquery)라고 한다. - 서브쿼리 = 자식쿼리 = 내부쿼리 : 외부 쿼리에 의해 실행되는 쿼리 - 메인쿼리 = 부모쿼리 = 외부쿼리 : 서브쿼리를 포함하고 있는 쿼리 서브쿼리는 다음과 같은 특징을 가지고 있다. - 다른 쿼리문의 결과를 이용하여 원하는 데이터를 조회하거나 조작할 때 사용된다 - 괄호`()` 로 감싸져서 표현 된다 - 괄호가 끝나고 끝에 세미콜론`;`을 붙이지 않는다 - 서브쿼리가 있는 SQL 문에서 서브쿼리는 먼저 실행되고 그 결과를 이용하여 외부 쿼리가 실행된다 - 쿼리문이 실행되면, 먼저 서브쿼리가 실행되고, 그 결과를 이용하여 외부쿼리가 실행된다. - `ORDER BY` 절을 사용할 수 없다. 서브쿼리는 또한 다음과 같은 종류로 나눌 수 있다. | 서브쿼리 종류 | 설명 | | -------------------------------- | ------------------------------------------------- | | 스칼라 서브쿼리(Scalar Subquery) | `SELECT` 절에 위치. 하나의 **칼럼처럼 동작** | | 인라인 뷰(Inline View) | `FROM` 절에 위치. 하나의 **테이블처럼 동작** | | 중첩 서브쿼리(Nested Subquery) | `WHERE` 절에 위치. 하나의 **변수(상수)처럼 동작** | ### 스칼라 서브쿼리(Scalar Subquery) `SELECT` 절에 위치하며 하나의 칼럼처럼 동작하는 서브쿼리를 **스칼라 서브쿼리(Scalar Subquery)**라고 한다. 스칼라 서브쿼리는 반드시 단일 행 or 단일 값으로 리턴되어야 한다. 다중 행 값이 조회되면 에러가 발생한다. 스칼라 서브쿼리는 다음과 같이 사용된다. ```sql SELECT ... , (SELECT ...) FROM table; ``` 예를 들어, `Employees` 테이블에서 직원 이름과 부서 이름을 함께 조회하되, 부서 이름이 `HR`인 직원만 조회하려면 다음과 같이 서브쿼리를 사용할 수 있다. ```sql SELECT Name, (SELECT DepartmentName FROM Departments WHERE DepartmentID = Employees.DepartmentID) FROM Employees; /* 결과 +-------+--------------------------------------------------------------------------------------+ | Name | (SELECT DepartmentName FROM Departments WHERE DepartmentID = Employees.DepartmentID) | +-------+--------------------------------------------------------------------------------------+ | Alice | HR | | Bob | HR | | Carol | Marketing | | David | NULL | +-------+--------------------------------------------------------------------------------------+ */ ``` 다만 위와 같이 서브쿼리를 사용하면 서브쿼리의 결과가 별도의 컬럼으로 표시된다. 따라서 일반적으로 서브쿼리의 결과에 별칭을 붙여서 사용한다. ```sql SELECT Name, (SELECT DepartmentName FROM Departments WHERE DepartmentID = Employees.DepartmentID) AS DepartmentName FROM Employees; /* 결과 +-------+----------------+ | Name | DepartmentName | +-------+----------------+ | Alice | HR | | Bob | HR | | Carol | Marketing | | David | NULL | +-------+----------------+ */ ``` 만약 다중 행 값이 조회되는 서브쿼리를 사용하면 에러가 발생한다. ```sql SELECT Name, (SELECT DepartmentName FROM Departments) FROM Employees; /* 에러 ERROR 1242 (21000): Subquery returns more than 1 row */ ``` ### `FROM` 절에서의 서브쿼리 : 인라인 뷰(Inline View) `FROM` 절에 위치하며 하나의 테이블처럼 동작하는 서브쿼리를 **인라인 뷰(Inline View)**라고 한다. 인라인 뷰는 반드시 별칭을 붙여야 한다. 그렇지 않으면 에러가 발생한다. 인라인 뷰 서브쿼리는 테이블의 일부 데이터를 추출하여 새로운 가상 테이블을 만들어 사용할 때 유용하다. 인라인 뷰는 다음과 같이 사용된다. ```sql SELECT ... FROM (SELECT ...) AS 별칭; ``` 예를 들어 `Employees` 테이블에서 부서 ID가 1인 직원만 조회하려면 다음과 같이 인라인 뷰를 사용할 수 있다. ```sql SELECT * FROM (SELECT * FROM Employees WHERE DepartmentID = 1) AS HR; /* 결과 +------------+-------+--------------+ | EmployeeID | Name | DepartmentID | +------------+-------+--------------+ | 1 | Alice | 1 | | 2 | Bob | 1 | +------------+-------+--------------+ */ ``` ### `WHERE` 절에서의 서브쿼리 : 중첩 서브쿼리(Nested Subquery) `WHERE` 절에 위치하며 하나의 변수(상수)처럼 동작하는 서브쿼리를 **중첩 서브쿼리(Nested Subquery)**라고 한다. 중첩 서브쿼리는 다음과 같이 사용된다. ```sql SELECT ... FROM table WHERE column = (SELECT ...); ``` 예를 들어 `Employees` 테이블에서 부서 이름이 `HR`인 직원만 조회하려면 다음과 같이 중첩 서브쿼리를 사용할 수 있다. ```sql SELECT * FROM Employees WHERE DepartmentID = (SELECT DepartmentID FROM Departments WHERE DepartmentName = 'HR'); /* 결과 +------------+-------+--------------+ | EmployeeID | Name | DepartmentID | +------------+-------+--------------+ | 1 | Alice | 1 | | 2 | Bob | 1 | +------------+-------+--------------+ */ ```