# 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`

`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 |
+------------+-------+--------------+
*/
```